gonum.org/v1/gonum@v0.15.1-0.20240517103525-f853624cb1bb/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  	"math"
     9  	"sort"
    10  
    11  	"gonum.org/v1/gonum/graph"
    12  	"gonum.org/v1/gonum/graph/iterator"
    13  )
    14  
    15  // YenKShortestPaths returns the k-shortest loopless paths from s to t in g
    16  // with path costs no greater than cost beyond the shortest path.
    17  // If k is negative, only path cost will be used to limit the set of returned
    18  // paths. YenKShortestPaths will panic if g contains a negative edge weight.
    19  func YenKShortestPaths(g graph.Graph, k int, cost float64, s, t graph.Node) [][]graph.Node {
    20  	// See https://en.wikipedia.org/wiki/Yen's_algorithm and
    21  	// the paper at https://doi.org/10.1090%2Fqam%2F253822.
    22  
    23  	_, isDirected := g.(graph.Directed)
    24  	yk := yenKSPAdjuster{
    25  		Graph:      g,
    26  		isDirected: isDirected,
    27  	}
    28  
    29  	if wg, ok := g.(Weighted); ok {
    30  		yk.weight = wg.Weight
    31  	} else {
    32  		yk.weight = UniformCost(g)
    33  	}
    34  
    35  	shortest, weight := DijkstraFrom(s, yk).To(t.ID())
    36  	cost += weight // Set cost to absolute cost limit.
    37  	switch len(shortest) {
    38  	case 0:
    39  		return nil
    40  	case 1:
    41  		return [][]graph.Node{shortest}
    42  	}
    43  	paths := [][]graph.Node{shortest}
    44  
    45  	var pot []yenShortest
    46  	var root []graph.Node
    47  	for i := int64(1); k < 0 || i < int64(k); i++ {
    48  		// The spur node ranges from the first node to the next
    49  		// to last node in the previous k-shortest path.
    50  		for n := 0; n < len(paths[i-1])-1; n++ {
    51  			yk.reset()
    52  
    53  			spur := paths[i-1][n]
    54  			root := append(root[:0], paths[i-1][:n+1]...)
    55  
    56  			for _, path := range paths {
    57  				if len(path) <= n {
    58  					continue
    59  				}
    60  				ok := true
    61  				for x := 0; x < len(root); x++ {
    62  					if path[x].ID() != root[x].ID() {
    63  						ok = false
    64  						break
    65  					}
    66  				}
    67  				if ok {
    68  					yk.removeEdge(path[n].ID(), path[n+1].ID())
    69  				}
    70  			}
    71  			for _, u := range root[:len(root)-1] {
    72  				yk.removeNode(u.ID())
    73  			}
    74  
    75  			spath, weight := DijkstraFrom(spur, yk).To(t.ID())
    76  			if weight > cost || math.IsInf(weight, 1) {
    77  				continue
    78  			}
    79  			if len(root) > 1 {
    80  				var rootWeight float64
    81  				for x := 1; x < len(root); x++ {
    82  					w, _ := yk.weight(root[x-1].ID(), root[x].ID())
    83  					rootWeight += w
    84  				}
    85  				spath = append(root[:len(root)-1], spath...)
    86  				weight += rootWeight
    87  			}
    88  
    89  			// Add the potential k-shortest path if it is new.
    90  			isNewPot := true
    91  			for x := range pot {
    92  				if isSamePath(pot[x].path, spath) {
    93  					isNewPot = false
    94  					break
    95  				}
    96  			}
    97  			if isNewPot {
    98  				pot = append(pot, yenShortest{spath, weight})
    99  			}
   100  		}
   101  
   102  		if len(pot) == 0 {
   103  			break
   104  		}
   105  
   106  		sort.Sort(byPathWeight(pot))
   107  		best := pot[0]
   108  		if len(best.path) <= 1 || best.weight > cost {
   109  			break
   110  		}
   111  		paths = append(paths, best.path)
   112  		pot = pot[1:]
   113  	}
   114  
   115  	return paths
   116  }
   117  
   118  func isSamePath(a, b []graph.Node) bool {
   119  	if len(a) != len(b) {
   120  		return false
   121  	}
   122  
   123  	for i, x := range a {
   124  		if x.ID() != b[i].ID() {
   125  			return false
   126  		}
   127  	}
   128  	return true
   129  }
   130  
   131  // yenShortest holds a path and its weight for sorting.
   132  type yenShortest struct {
   133  	path   []graph.Node
   134  	weight float64
   135  }
   136  
   137  type byPathWeight []yenShortest
   138  
   139  func (s byPathWeight) Len() int           { return len(s) }
   140  func (s byPathWeight) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   141  func (s byPathWeight) Less(i, j int) bool { return s[i].weight < s[j].weight }
   142  
   143  // yenKSPAdjuster allows walked edges to be omitted from a graph
   144  // without altering the embedded graph.
   145  type yenKSPAdjuster struct {
   146  	graph.Graph
   147  	isDirected bool
   148  
   149  	// weight is the edge weight function
   150  	// used for shortest path calculation.
   151  	weight Weighting
   152  
   153  	// visitedNodes holds the nodes that have
   154  	// been removed by Yen's algorithm.
   155  	visitedNodes map[int64]struct{}
   156  
   157  	// visitedEdges holds the edges that have
   158  	// been removed by Yen's algorithm.
   159  	visitedEdges map[[2]int64]struct{}
   160  }
   161  
   162  func (g yenKSPAdjuster) From(id int64) graph.Nodes {
   163  	if _, blocked := g.visitedNodes[id]; blocked {
   164  		return graph.Empty
   165  	}
   166  	nodes := graph.NodesOf(g.Graph.From(id))
   167  	for i := 0; i < len(nodes); {
   168  		if g.canWalk(id, nodes[i].ID()) {
   169  			i++
   170  			continue
   171  		}
   172  		nodes[i] = nodes[len(nodes)-1]
   173  		nodes = nodes[:len(nodes)-1]
   174  	}
   175  	if len(nodes) == 0 {
   176  		return graph.Empty
   177  	}
   178  	return iterator.NewOrderedNodes(nodes)
   179  }
   180  
   181  func (g yenKSPAdjuster) canWalk(u, v int64) bool {
   182  	if _, blocked := g.visitedNodes[v]; blocked {
   183  		return false
   184  	}
   185  	_, blocked := g.visitedEdges[[2]int64{u, v}]
   186  	return !blocked
   187  }
   188  
   189  func (g yenKSPAdjuster) removeNode(u int64) {
   190  	g.visitedNodes[u] = struct{}{}
   191  }
   192  
   193  func (g yenKSPAdjuster) removeEdge(u, v int64) {
   194  	g.visitedEdges[[2]int64{u, v}] = struct{}{}
   195  	if !g.isDirected {
   196  		g.visitedEdges[[2]int64{v, u}] = struct{}{}
   197  	}
   198  }
   199  
   200  func (g *yenKSPAdjuster) reset() {
   201  	g.visitedNodes = make(map[int64]struct{})
   202  	g.visitedEdges = make(map[[2]int64]struct{})
   203  }
   204  
   205  func (g yenKSPAdjuster) Weight(xid, yid int64) (w float64, ok bool) {
   206  	return g.weight(xid, yid)
   207  }