package graphimport ()varErrTargetNotReachable = errors.New("target vertex not reachable from source")// CreatesCycle determines whether adding an edge between the two given vertices// would introduce a cycle in the graph. CreatesCycle will not create an edge.//// A potential edge would create a cycle if the target vertex is also a parent// of the source vertex. In order to determine this, CreatesCycle runs a DFS.func [ comparable, any]( Graph[, ], , ) (bool, error) {if , := .Vertex(); != nil {returnfalse, fmt.Errorf("could not get vertex with hash %v: %w", , ) }if , := .Vertex(); != nil {returnfalse, fmt.Errorf("could not get vertex with hash %v: %w", , ) }if == {returntrue, nil } , := .PredecessorMap()if != nil {returnfalse, fmt.Errorf("failed to get predecessor map: %w", ) } := make([], 0) := make(map[]bool) = append(, )forlen() > 0 { := [len()-1] = [:len()-1]if , := []; ! {// If the adjacent vertex also is the target vertex, the target is a // parent of the source vertex. An edge would introduce a cycle.if == {returntrue, nil } [] = truefor := range [] { = append(, ) } } }returnfalse, nil}// ShortestPath computes the shortest path between a source and a target vertex// under consideration of the edge weights. It returns a slice of hash values of// the vertices forming that path.//// The returned path includes the source and target vertices. If the target is// not reachable from the source, ErrTargetNotReachable will be returned. Should// there be multiple shortest paths, and arbitrary one will be returned.//// ShortestPath has a time complexity of O(|V|+|E|log(|V|)).func [ comparable, any]( Graph[, ], , ) ([], error) { := make(map[]float64) := make(map[]bool) [] = 0 [] = true := newPriorityQueue[]() , := .AdjacencyMap()if != nil {returnnil, fmt.Errorf("could not get adjacency map: %w", ) }for := range {if != { [] = math.Inf(1) [] = false } .Push(, []) }// bestPredecessors stores the cheapest or least-weighted predecessor for // each vertex. Given an edge AC with weight=4 and an edge BC with weight=2, // the cheapest predecessor for C is B. := make(map[])for .Len() > 0 { , := .Pop() := math.IsInf([], 1)for , := range [] { := .Properties.Weight// Setting the weight to 1 is required for unweighted graphs whose // edge weights are 0. Otherwise, all paths would have a sum of 0 // and a random path would be returned.if !.Traits().IsWeighted { = 1 } := [] + float64()if < [] && ! { [] = [] = .UpdatePriority(, ) } } } := []{} := for != {// If the current vertex is not present in bestPredecessors, current is // set to the zero value of K. Without this check, this would lead to an // endless prepending of zero values to the path. Also, the target would // not be reachable from one of the preceding vertices.if , := []; ! {returnnil, ErrTargetNotReachable } = [] = append([]{}, ...) }return , nil}type sccState[ comparable] struct { adjacencyMap map[]map[]Edge[] components [][] stack [] onStack map[]bool visited map[]struct{} lowlink map[]int index map[]int time int}// StronglyConnectedComponents detects all strongly connected components within// the graph and returns the hashes of the vertices shaping these components, so// each component is represented by a []K.//// StronglyConnectedComponents can only run on directed graphs.func [ comparable, any]( Graph[, ]) ([][], error) {if !.Traits().IsDirected {returnnil, errors.New("SCCs can only be detected in directed graphs") } , := .AdjacencyMap()if != nil {returnnil, fmt.Errorf("could not get adjacency map: %w", ) } := &sccState[]{adjacencyMap: ,components: make([][], 0),stack: make([], 0),onStack: make(map[]bool),visited: make(map[]struct{}),lowlink: make(map[]int),index: make(map[]int), }for := range .adjacencyMap {if , := .visited[]; ! {findSCC(, ) } }return .components, nil}func findSCC[ comparable]( , *sccState[]) { .stack = append(.stack, ) .onStack[] = true .visited[] = struct{}{} .index[] = .time .lowlink[] = .time .time++for := range .adjacencyMap[] {if , := .visited[]; ! { (, ) := math.Min(float64(.lowlink[]),float64(.lowlink[]), ) .lowlink[] = int() } else {// If the adjacent vertex already is on the stack, the edge joining // the current and the adjacent vertex is a back ege. Therefore, the // lowlink value of the vertex has to be updated to the index of the // adjacent vertex if it is smaller than the current lowlink value.if .onStack[] { := math.Min(float64(.lowlink[]),float64(.index[]), ) .lowlink[] = int() } } }// If the lowlink value of the vertex is equal to its DFS value, this is the // head vertex of a strongly connected component that's shaped by the vertex // and all vertices on the stack.if .lowlink[] == .index[] {varvar []for != { = .stack[len(.stack)-1] .stack = .stack[:len(.stack)-1] .onStack[] = false = append(, ) } .components = append(.components, ) }}// AllPathsBetween computes and returns all paths between two given vertices. A// path is represented as a slice of vertex hashes. The returned slice contains// these paths.//// AllPathsBetween utilizes a non-recursive, stack-based implementation. It has// an estimated runtime complexity of O(n^2) where n is the number of vertices.func [ comparable, any]( Graph[, ], , ) ([][], error) { , := .AdjacencyMap()if != nil {returnnil, }// The algorithm used relies on stacks instead of recursion. It is described // here: https://boycgit.github.io/all-paths-between-two-vertex/ := newStack[]() := newStack[stack[]]() := func() error {if .isEmpty() || .isEmpty() {returnerrors.New("empty stack") }returnnil } := func( ) { .push() := newStack[]()for := range [] {varbool .forEach(func( ) {if == { = true } })if {continue } .push() } .push() } := func() error {if = (); != nil {returnfmt.Errorf("unable to build stack: %w", ) } , := .top()for !.isEmpty() { , := .pop() () , _ = .top() }returnnil } := func() error {if = (); != nil {returnfmt.Errorf("unable to remove layer: %w", ) }if , := .top(); !.isEmpty() {returnerrors.New("the top element of vice-stack is not empty") } _, _ = .pop() _, _ = .pop()returnnil } () := make([][], 0)for !.isEmpty() { , := .top() , := .top()if .isEmpty() {if == { := make([], 0) .forEach(func( ) { = append(, ) }) = append(, ) } = ()if != nil {returnnil, } } else {if = (); != nil {returnnil, } } }return , nil}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.