cuelang.org/go@v0.13.0/internal/core/toposort/scc.go (about) 1 // Copyright 2024 CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package toposort 16 17 import ( 18 "slices" 19 ) 20 21 type sccNodeState struct { 22 component *StronglyConnectedComponent 23 lowLink uint 24 index uint 25 visited bool 26 onStack bool 27 } 28 29 type StronglyConnectedComponent struct { 30 Nodes Nodes 31 Outgoing []*StronglyConnectedComponent 32 Incoming []*StronglyConnectedComponent 33 visited bool 34 } 35 36 // Calculate the Strongly Connected Components of the graph. 37 // https://en.wikipedia.org/wiki/Strongly_connected_component 38 // 39 // The components returned are topologically sorted (forwards), and 40 // form a DAG (this is the "condensation graph"). 41 func (graph *Graph) StronglyConnectedComponents() []*StronglyConnectedComponent { 42 nodeStates := make([]sccNodeState, len(graph.nodes)) 43 for i, node := range graph.nodes { 44 node.sccNodeState = &nodeStates[i] 45 } 46 47 scc := &sccFinderState{} 48 for _, node := range graph.nodes { 49 if !node.sccNodeState.visited { 50 scc.findSCC(node) 51 } 52 } 53 54 for _, node := range graph.nodes { 55 node.sccNodeState = nil 56 } 57 58 components := scc.components 59 for _, component := range components { 60 for _, next := range component.Outgoing { 61 next.Incoming = append(next.Incoming, component) 62 } 63 } 64 slices.Reverse(components) 65 return components 66 } 67 68 type sccFinderState struct { 69 components []*StronglyConnectedComponent 70 stack Nodes 71 counter uint 72 } 73 74 // This is Tarjan's algorithm from 1972. 75 // 76 // Robert Tarjan: Depth-first search and linear graph algorithms. 77 // SIAM Journal on Computing. Volume 1, Nr. 2 (1972), pp. 146-160. 78 // 79 // https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm 80 func (scc *sccFinderState) findSCC(cur *Node) { 81 num := scc.counter 82 scc.counter++ 83 84 curScc := cur.sccNodeState 85 curScc.lowLink = num 86 curScc.index = num 87 curScc.visited = true 88 curScc.onStack = true 89 90 scc.stack = append(scc.stack, cur) 91 92 for _, next := range cur.Outgoing { 93 nextScc := next.sccNodeState 94 if !nextScc.visited { 95 scc.findSCC(next) 96 curScc.lowLink = min(curScc.lowLink, nextScc.lowLink) 97 98 } else if nextScc.onStack { 99 // If the next node is already on the stack, the edge joining 100 // the current node and the next node completes a cycle. 101 curScc.lowLink = min(curScc.lowLink, nextScc.index) 102 } 103 } 104 105 // If the lowlink value of the node is equal to its DFS value, this 106 // is the head node of a strongly connected component that's shaped 107 // by the node and all nodes on the stack. 108 if curScc.lowLink == curScc.index { 109 component := &StronglyConnectedComponent{visited: true} 110 111 var componentNodes Nodes 112 113 for i := len(scc.stack) - 1; i >= 0; i-- { 114 nodeN := scc.stack[i] 115 nodeNScc := nodeN.sccNodeState 116 nodeNScc.onStack = false 117 nodeNScc.component = component 118 componentNodes = append(componentNodes, nodeN) 119 if nodeNScc == curScc { 120 scc.stack = scc.stack[:i] 121 break 122 } 123 } 124 125 var outgoingComponents []*StronglyConnectedComponent 126 for _, node := range componentNodes { 127 for _, nextNode := range node.Outgoing { 128 // This algorithm is depth-first, which means we can rely 129 // on the next component always existing before our own 130 // component. 131 nextComponent := nextNode.sccNodeState.component 132 if !nextComponent.visited { 133 nextComponent.visited = true 134 outgoingComponents = append(outgoingComponents, nextComponent) 135 } 136 } 137 } 138 139 component.Nodes = componentNodes 140 component.Outgoing = outgoingComponents 141 component.visited = false 142 for _, component := range outgoingComponents { 143 component.visited = false 144 } 145 scc.components = append(scc.components, component) 146 } 147 }