github.com/gopherd/gonum@v0.0.4/graph/path/yen_ksp.go (about) 1 // Copyright ©2018 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package path 6 7 import ( 8 "sort" 9 10 "github.com/gopherd/gonum/graph" 11 "github.com/gopherd/gonum/graph/iterator" 12 ) 13 14 // YenKShortestPaths returns the k-shortest loopless paths from s to t in g. 15 // YenKShortestPaths will panic if g contains a negative edge weight. 16 func YenKShortestPaths(g graph.Graph, k int, s, t graph.Node) [][]graph.Node { 17 // See https://en.wikipedia.org/wiki/Yen's_algorithm and 18 // the paper at https://doi.org/10.1090%2Fqam%2F253822. 19 20 _, isDirected := g.(graph.Directed) 21 yk := yenKSPAdjuster{ 22 Graph: g, 23 isDirected: isDirected, 24 } 25 26 if wg, ok := g.(Weighted); ok { 27 yk.weight = wg.Weight 28 } else { 29 yk.weight = UniformCost(g) 30 } 31 32 shortest, _ := DijkstraFrom(s, yk).To(t.ID()) 33 switch len(shortest) { 34 case 0: 35 return nil 36 case 1: 37 return [][]graph.Node{shortest} 38 } 39 paths := [][]graph.Node{shortest} 40 41 var pot []yenShortest 42 var root []graph.Node 43 for i := int64(1); i < int64(k); i++ { 44 for n := 0; n < len(paths[i-1])-2; n++ { 45 yk.reset() 46 47 spur := paths[i-1][n] 48 root := append(root[:0], paths[i-1][:n+1]...) 49 50 for _, path := range paths { 51 if len(path) <= n { 52 continue 53 } 54 ok := true 55 for x := 0; x < len(root); x++ { 56 if path[x].ID() != root[x].ID() { 57 ok = false 58 break 59 } 60 } 61 if ok { 62 yk.removeEdge(path[n].ID(), path[n+1].ID()) 63 } 64 } 65 66 spath, weight := DijkstraFrom(spur, yk).To(t.ID()) 67 if len(root) > 1 { 68 var rootWeight float64 69 for x := 1; x < len(root); x++ { 70 w, _ := yk.weight(root[x-1].ID(), root[x].ID()) 71 rootWeight += w 72 } 73 root = append(root[:len(root)-1], spath...) 74 pot = append(pot, yenShortest{root, weight + rootWeight}) 75 } else { 76 pot = append(pot, yenShortest{spath, weight}) 77 } 78 } 79 80 if len(pot) == 0 { 81 break 82 } 83 84 sort.Sort(byPathWeight(pot)) 85 best := pot[0].path 86 if len(best) <= 1 { 87 break 88 } 89 paths = append(paths, best) 90 pot = pot[1:] 91 } 92 93 return paths 94 } 95 96 // yenShortest holds a path and its weight for sorting. 97 type yenShortest struct { 98 path []graph.Node 99 weight float64 100 } 101 102 type byPathWeight []yenShortest 103 104 func (s byPathWeight) Len() int { return len(s) } 105 func (s byPathWeight) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 106 func (s byPathWeight) Less(i, j int) bool { return s[i].weight < s[j].weight } 107 108 // yenKSPAdjuster allows walked edges to be omitted from a graph 109 // without altering the embedded graph. 110 type yenKSPAdjuster struct { 111 graph.Graph 112 isDirected bool 113 114 // weight is the edge weight function 115 // used for shortest path calculation. 116 weight Weighting 117 118 // visitedEdges holds the edges that have 119 // been removed by Yen's algorithm. 120 visitedEdges map[[2]int64]struct{} 121 } 122 123 func (g yenKSPAdjuster) From(id int64) graph.Nodes { 124 nodes := graph.NodesOf(g.Graph.From(id)) 125 for i := 0; i < len(nodes); { 126 if g.canWalk(id, nodes[i].ID()) { 127 i++ 128 continue 129 } 130 nodes[i] = nodes[len(nodes)-1] 131 nodes = nodes[:len(nodes)-1] 132 } 133 return iterator.NewOrderedNodes(nodes) 134 } 135 136 func (g yenKSPAdjuster) canWalk(u, v int64) bool { 137 _, ok := g.visitedEdges[[2]int64{u, v}] 138 return !ok 139 } 140 141 func (g yenKSPAdjuster) removeEdge(u, v int64) { 142 g.visitedEdges[[2]int64{u, v}] = struct{}{} 143 if g.isDirected { 144 g.visitedEdges[[2]int64{v, u}] = struct{}{} 145 } 146 } 147 148 func (g *yenKSPAdjuster) reset() { 149 g.visitedEdges = make(map[[2]int64]struct{}) 150 } 151 152 func (g yenKSPAdjuster) Weight(xid, yid int64) (w float64, ok bool) { 153 return g.weight(xid, yid) 154 }