The shortest path dixtra algorithm for the basic data structure and algorithm graph of hand golang

Time:2021-10-14

origin

Recently read < < my first algorithm book > > ([Japan] Ishida Baohui; Miyazaki Xiuyi)
This series of notes is intended to use golang exercises

Dixtra algorithm

Similar to the Behrman Ford algorithm,
Dijkstra algorithm is also an algorithm for solving the shortest path problem,
It can be used to find the path with the smallest sum of weights in the path from the start point to the end point.

Compared with the Behrman Ford algorithm, which needs to repeatedly calculate and update the weights for all edges,
Dixtra algorithm has one more step to select vertices,
This makes it more efficient in finding the shortest path.

If there is a negative weight in the closed loop, there is no shortest path.
Behrman Ford algorithm can directly determine that there is no shortest path,
But in dixtra algorithm, even if there is no shortest path,
It will also calculate a wrong shortest path.
Therefore, the dixtra algorithm cannot be used when there are negative weights.

From < < my first algorithm book > > [Japan] Ishida Baohui; Miyazaki Xiuyi
  • Dixtra algorithm is very similar to Behrman Ford algorithm, the main difference is that the candidate node with the smallest weight is always preferred
  • Therefore, Behrman Ford algorithm uses queue or stack to store candidate nodes, while dixtra algorithm uses heap

technological process

  1. Given several vertices and several edges between vertices, find the minimum weight path from the specified starting point srcnode to the specified ending point dstnode
  2. Set the weight of srcnode to 0 and the weight of other vertices to infinity
  3. Send the srcnode node into the candidate heap
  4. For candidate heap is not empty:

    1. Pop vertex node from candidate heap
    2. If node.id = = dstnode.id, the loop ends
    3. Traverse all edges starting from node, and update the weight of the end point to of the edge to min (end point weight, node. Weight + edge. Weight)
    4. If to. Weight > node. Weight + edge. Weight, the update is valid
    5. If the update is valid, judge whether to is in the heap. If yes, float up to maintain heap order. Otherwise, push the to node into the candidate heap
  5. Judge whether the weight of the end point is updated (! = infinity). If yes, it indicates that there is the shortest path
  6. Reverse find shortest path:

    1. Set current = end point of current node
    2. Push node current into path queue
    3. Traverse the edge whose end point is current and find the qualified node: the starting point of the edge. Weight = current. Weight – edge. Weight
    4. Push node into path queue
    5. Loop 1-4 until current = = srcnode, and the lookup is completed

Design

  • Inode: vertex interface
  • Iline: edge interface
  • Ipathfinder: shortest path lookup algorithm interface
  • Icomparator: vertex comparison interface
  • Iheap: vertex heap interface
  • Tnode: vertex, implementation inode
  • Tline: edge, implement iline
  • Tnodeweightcomparator: a weight based vertex comparator that implements the icomparator interface
  • Tarrayheap: implementation of heap
  • Tdijkstrapathfinder: implementation of Dijkstra algorithm

unit testing

dijkstra_finder_test.go

package graph

import (
    "fmt"
    dk "learning/gooop/graph/dijkstra"
    "strings"
    "testing"
)

func Test_DijkstraFinder(t *testing.T) {
    fnAssertTrue := func(b bool, msg string) {
        if !b {
            t.Fatal(msg)
        }
    }

    nodes := []dk.INode{
        dk.NewNode("a"),
        dk.NewNode("b"),
        dk.NewNode("c"),
        dk.NewNode("d"),
        dk.NewNode("e"),
        dk.NewNode("f"),
        dk.NewNode("g"),
    }

    lines := []dk.ILine {
        dk.NewLine("a", "b", 9),
        dk.NewLine("a", "c", 2),

        dk.NewLine("b", "c", 6),
        dk.NewLine("b", "d", 3),
        dk.NewLine("b", "e", 1),

        dk.NewLine("c", "d", 2),
        dk.NewLine("c", "f", 9),

        dk.NewLine("d", "e", 5),
        dk.NewLine("d", "f", 6),

        dk.NewLine("e", "f", 3),
        dk.NewLine("e", "g", 7),

        dk.NewLine("f", "g", 4),
    }

    for _,it := range lines[:] {
        lines = append(lines, dk.NewLine(it.To(), it.From(), it.Weight()))
    }

    ok,path := dk.DijkstraPathFinder.FindPath(nodes, lines, "a", "g")
    if !ok {
        t.Fatal("failed to find min path")
    }
    fnPathToString := func(nodes []dk.INode) string {
        items := make([]string, len(nodes))
        for i,it := range nodes {
            items[i] = fmt.Sprintf("%s", it)
        }
        return strings.Join(items, " ")
    }
    pathString := fnPathToString(path)
    t.Log(pathString)
    fnAssertTrue(pathString == "a(0) c(2) d(4) f(10) g(14)", "incorrect path")
}

Test output

$ go test -v dijkstra_finder_test.go 
=== RUN   Test_DijkstraFinder
    dijkstra_finder_test.go:63: a(0) c(2) d(4) f(10) g(14)
--- PASS: Test_DijkstraFinder (0.00s)
PASS
ok      command-line-arguments  0.001s

INode.go

Vertex interface

package dijkstra

type INode interface {
    ID() string
    GetWeight() int
    SetWeight(int)
}

const MaxWeight = int(0x7fffffff_ffffffff)

ILine.go

Edge interface

package dijkstra

type ILine interface {
    From() string
    To() string
    Weight() int
}

IPathFinder.go

Shortest path lookup algorithm interface

package dijkstra

type IPathFinder interface {
    FindPath(nodes []INode, lines []ILine, from string, to string) (bool,[]INode)
}

IComparator.go

Vertex comparison interface

package dijkstra

type IComparator interface {
    Less(a interface{}, b interface{}) bool
}

IHeap.go

Vertex heap interface

package dijkstra

type IHeap interface {
    Size() int
    IsEmpty() bool
    IsNotEmpty() bool

    Push(node interface{})
    Pop() (bool, interface{})

    IndexOf(node interface{}) int
    ShiftUp(i int)
}

tNode.go

Vertex, implementation inode

package dijkstra

import "fmt"

type tNode struct {
    id string
    weight int
}

func NewNode(id string) INode {
    return &tNode{
        id,MaxWeight,
    }
}

func (me *tNode) ID() string {
    return me.id
}

func (me *tNode) GetWeight() int {
    return me.weight
}

func (me *tNode) SetWeight(w int) {
    me.weight = w
}

func (me *tNode) String() string {
    return fmt.Sprintf("%s(%v)", me.id, me.weight)
}

tLine.go

Edge, implement iline

package dijkstra

type tLine struct {
    from string
    to string
    weight int
}

func NewLine(from string, to string, weight int) ILine {
    return &tLine{
        from,to,weight,
    }
}

func (me *tLine) From() string {
    return me.from
}

func (me *tLine) To() string {
    return me.to
}

func (me *tLine) Weight() int {
    return me.weight
}

tNodeWeightComparator.go

The vertex comparator based on weight realizes icomparator interface

package dijkstra

import "errors"


type tNodeWeightComparator struct {
}

func newNodeWeightComparator() IComparator {
    return &tNodeWeightComparator{
    }
}

func (me *tNodeWeightComparator) Less(a interface{}, b interface{}) bool {
    if a == nil || b == nil {
        panic(gNullArgumentError)
    }

    n1 := a.(INode)
    n2 := b.(INode)
    return n1.GetWeight() <= n2.GetWeight()
}

var gNullArgumentError = errors.New("null argument error")

tArrayHeap.go

Implementation of heap

package dijkstra

import (
    "errors"
    "fmt"
    "strings"
)

type tArrayHeap struct {
    comparator IComparator
    items []interface{}
    size int
    version int64
}

func newArrayHeap(comparator IComparator) IHeap {
    return &tArrayHeap{
        comparator: comparator,
        items: make([]interface{}, 0),
        size: 0,
        version: 0,
    }
}

func (me *tArrayHeap) Size() int {
    return me.size
}

func (me *tArrayHeap) IsEmpty() bool {
    return me.size <= 0
}

func (me *tArrayHeap) IsNotEmpty() bool {
    return !me.IsEmpty()
}

func (me *tArrayHeap) Push(value interface{}) {
    me.version++

    me.ensureSize(me.size + 1)
    me.items[me.size] = value
    me.size++

    me.ShiftUp(me.size - 1)
    me.version++
}


func (me *tArrayHeap) ensureSize(size int) {
    for ;len(me.items) < size; {
        me.items = append(me.items, nil)
    }
}

func (me *tArrayHeap) parentOf(i int) int {
    return (i - 1) / 2
}

func (me *tArrayHeap) leftChildOf(i int) int {
    return i*2 + 1
}

func (me *tArrayHeap) rightChildOf(i int) int {
    return me.leftChildOf(i) + 1
}

func (me *tArrayHeap) last() (i int, v interface{}) {
    if me.IsEmpty() {
        return -1, nil
    }

    i = me.size - 1
    v = me.items[i]
    return i,v
}

func (me *tArrayHeap) IndexOf(node interface{}) int {
    n := -1
    for i,it := range me.items {
        if it == node {
            n = i
            break
        }
    }

    return n
}

func (me *tArrayHeap) ShiftUp(i int) {
    if i <= 0 {
        return
    }
    v := me.items[i]

    pi := me.parentOf(i)
    pv := me.items[pi]

    if me.comparator.Less(v, pv) {
        me.items[pi], me.items[i] = v, pv
        me.ShiftUp(pi)
    }
}

func (me *tArrayHeap) Pop() (bool, interface{}) {
    if me.IsEmpty() {
        return false, nil
    }

    me.version++

    top := me.items[0]
    li, lv := me.last()
    me.items[0] = nil
    me.size--

    if me.IsEmpty() {
        return true, top
    }

    me.items[0] = lv
    me.items[li] = nil

    me.shiftDown(0)
    me.version++

    return true, top
}

func (me *tArrayHeap) shiftDown(i int) {
    pv := me.items[i]
    ok, ci, cv := me.minChildOf(i)
    if ok && me.comparator.Less(cv, pv) {
        me.items[i], me.items[ci] = cv, pv
        me.shiftDown(ci)
    }
}

func (me *tArrayHeap) minChildOf(p int) (ok bool, i int, v interface{}) {
    li := me.leftChildOf(p)
    if li >= me.size {
        return false, 0, nil
    }
    lv := me.items[li]

    ri := me.rightChildOf(p)
    if ri >= me.size {
        return true, li, lv
    }
    rv := me.items[ri]

    if me.comparator.Less(lv, rv) {
        return true, li, lv
    } else {
        return true, ri, rv
    }
}

func (me *tArrayHeap) String() string {
    level := 0
    lines := make([]string, 0)
    lines = append(lines, "")

    for {
        n := 1<<level
        min := n - 1
        max := n + min - 1
        if min >= me.size {
            break
        }

        line := make([]string, 0)
        for i := min;i <= max;i++ {
            if i >= me.size {
                break
            }
            line = append(line, fmt.Sprintf("%4d", me.items[i]))
        }
        lines = append(lines, strings.Join(line, ","))

        level++
    }

    return strings.Join(lines, "\n")
}

var gNoMoreElementsError = errors.New("no more elements")

tDijkstraPathFinder.go

Implementation of dixtra algorithm

package dijkstra

type tDijkstraPathFinder struct {
}

func newDijkstraPathFinder() IPathFinder {
    return &tDijkstraPathFinder{}
}

func (me *tDijkstraPathFinder) FindPath(nodes []INode, lines []ILine, srcID string, dstID string) (bool,[]INode) {
    //Node index
    mapNodes := make(map[string]INode, 0)
    for _,it := range nodes {
        mapNodes[it.ID()] = it
    }

    srcNode, ok := mapNodes[srcID]
    if !ok {
        return false, nil
    }

    dstNode,ok := mapNodes[dstID]
    if !ok {
        return false, nil
    }

    //Index of edges
    mapFromLines := make(map[string][]ILine, 0)
    mapToLines := make(map[string][]ILine, 0)
    for _, it := range lines {
        if v,ok := mapFromLines[it.From()];ok {
            mapFromLines[it.From()] = append(v, it)
        } else {
            mapFromLines[it.From()] = []ILine{ it }
        }

        if v,ok := mapToLines[it.To()];ok {
            mapToLines[it.To()] = append(v, it)
        } else {
            mapToLines[it.To()] = []ILine{ it }
        }
    }

    //Set the weight of the from node to 0 and the weight of other nodes to maxweight
    for _,it := range nodes {
        if it.ID() == srcID {
            it.SetWeight(0)
        } else {
            it.SetWeight(MaxWeight)
        }
    }

    //Push the starting point to the heap
    heap := newArrayHeap(newNodeWeightComparator())
    heap.Push(srcNode)

    //Traverse candidate nodes
    for heap.IsNotEmpty() {
        _, top := heap.Pop()
        from := top.(INode)
        if from.ID() == dstID {
            break
        }

        links, ok := mapFromLines[from.ID()]
        if ok {
            for _,line := range links {
                if to,ok := mapNodes[line.To()];ok {
                    if me.updateWeight(from, to, line) {
                        n := heap.IndexOf(to)
                        if n >= 0 {
                            heap.ShiftUp(n)
                        } else {
                            heap.Push(to)
                        }
                    }
                }
            }
        }
    }

    //Reverse find shortest path
    if dstNode.GetWeight() >= MaxWeight {
        return false, nil
    }

    path := []INode{ dstNode }
    current := dstNode
    maxRound := len(lines)
    for ;current != srcNode && maxRound > 0;maxRound-- {
        linkedLines, _ := mapToLines[current.ID()]
        for _,line := range linkedLines {
            from, _ := mapNodes[line.From()]
            if from.GetWeight() == current.GetWeight() - line.Weight() {
                current = from
                path = append(path, from)
            }
        }
    }

    if current != srcNode {
        return false, nil
    }

    me.reverse(path)
    return true, path
}


func (me *tDijkstraPathFinder) reverse(nodes []INode) {
    for i,j := 0, len(nodes)-1;i < j;i,j=i+1,j-1 {
        nodes[i], nodes[j] = nodes[j], nodes[i]
    }
}

func (me *tDijkstraPathFinder) updateWeight(from INode, to INode, line ILine) bool {
    w := me.min(from.GetWeight() + line.Weight(), to.GetWeight())
    if to.GetWeight() > w {
        to.SetWeight(w)
        return true
    }

    return false
}


func (me *tDijkstraPathFinder) min(a, b int) int {
    if a <= b {
        return a
    }
    return b
}


var DijkstraPathFinder = newDijkstraPathFinder()

(end)