blob: 315c28f2e14f6c62724e8207aea448eb5293a577 [file] [log] [blame]
// Copyright ©2018 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package path
import (
"sort"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/iterator"
)
// YenKShortestPaths returns the k-shortest loopless paths from s to t in g.
// YenKShortestPaths will panic if g contains a negative edge weight.
func YenKShortestPaths(g graph.Graph, k int, s, t graph.Node) [][]graph.Node {
_, isDirected := g.(graph.Directed)
yk := yenKSPAdjuster{
Graph: g,
isDirected: isDirected,
}
if wg, ok := g.(Weighted); ok {
yk.weight = wg.Weight
} else {
yk.weight = UniformCost(g)
}
shortest, _ := DijkstraFrom(s, yk).To(t.ID())
switch len(shortest) {
case 0:
return nil
case 1:
return [][]graph.Node{shortest}
}
paths := [][]graph.Node{shortest}
var pot []yenShortest
var root []graph.Node
for i := int64(1); i < int64(k); i++ {
for n := 0; n < len(paths[i-1])-1; n++ {
yk.reset()
spur := paths[i-1][n]
root := append(root[:0], paths[i-1][:n+1]...)
for _, path := range paths {
if len(path) <= n {
continue
}
ok := true
for x := 0; x < len(root); x++ {
if path[x].ID() != root[x].ID() {
ok = false
break
}
}
if ok {
yk.removeEdge(path[n].ID(), path[n+1].ID())
}
}
spath, weight := DijkstraFrom(spur, yk).To(t.ID())
if len(root) > 1 {
var rootWeight float64
for x := 1; x < len(root); x++ {
w, _ := yk.weight(root[x-1].ID(), root[x].ID())
rootWeight += w
}
root = append(root[:len(root)-1], spath...)
pot = append(pot, yenShortest{root, weight + rootWeight})
} else {
pot = append(pot, yenShortest{spath, weight})
}
}
if len(pot) == 0 {
break
}
sort.Sort(byPathWeight(pot))
best := pot[0].path
if len(best) <= 1 {
break
}
paths = append(paths, best)
pot = pot[1:]
}
return paths
}
// yenShortest holds a path and its weight for sorting.
type yenShortest struct {
path []graph.Node
weight float64
}
type byPathWeight []yenShortest
func (s byPathWeight) Len() int { return len(s) }
func (s byPathWeight) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byPathWeight) Less(i, j int) bool { return s[i].weight < s[j].weight }
// yenKSPAdjuster allows walked edges to be omitted from a graph
// without altering the embedded graph.
type yenKSPAdjuster struct {
graph.Graph
isDirected bool
// weight is the edge weight function
// used for shortest path calculation.
weight Weighting
// visitedEdges holds the edges that have
// been removed by Yen's algorithm.
visitedEdges map[[2]int64]struct{}
}
func (g yenKSPAdjuster) From(id int64) graph.Nodes {
nodes := graph.NodesOf(g.Graph.From(id))
for i := 0; i < len(nodes); {
if g.canWalk(id, nodes[i].ID()) {
i++
continue
}
nodes[i] = nodes[len(nodes)-1]
nodes = nodes[:len(nodes)-1]
}
return iterator.NewOrderedNodes(nodes)
}
func (g yenKSPAdjuster) canWalk(u, v int64) bool {
_, ok := g.visitedEdges[[2]int64{u, v}]
return !ok
}
func (g yenKSPAdjuster) removeEdge(u, v int64) {
g.visitedEdges[[2]int64{u, v}] = struct{}{}
if g.isDirected {
g.visitedEdges[[2]int64{v, u}] = struct{}{}
}
}
func (g *yenKSPAdjuster) reset() {
g.visitedEdges = make(map[[2]int64]struct{})
}
func (g yenKSPAdjuster) Weight(xid, yid int64) (w float64, ok bool) {
return g.weight(xid, yid)
}