| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package dag |
| |
| // StronglyConnected returns the list of strongly connected components |
| // within the Graph g. This information is primarily used by this package |
| // for cycle detection, but strongly connected components have widespread |
| // use. |
| func StronglyConnected(g *Graph) [][]Vertex { |
| vs := g.Vertices() |
| acct := sccAcct{ |
| NextIndex: 1, |
| VertexIndex: make(map[Vertex]int, len(vs)), |
| } |
| for _, v := range vs { |
| // Recurse on any non-visited nodes |
| if acct.VertexIndex[v] == 0 { |
| stronglyConnected(&acct, g, v) |
| } |
| } |
| return acct.SCC |
| } |
| |
| func stronglyConnected(acct *sccAcct, g *Graph, v Vertex) int { |
| // Initial vertex visit |
| index := acct.visit(v) |
| minIdx := index |
| |
| for _, raw := range g.downEdgesNoCopy(v) { |
| target := raw.(Vertex) |
| targetIdx := acct.VertexIndex[target] |
| |
| // Recurse on successor if not yet visited |
| if targetIdx == 0 { |
| minIdx = min(minIdx, stronglyConnected(acct, g, target)) |
| } else if acct.inStack(target) { |
| // Check if the vertex is in the stack |
| minIdx = min(minIdx, targetIdx) |
| } |
| } |
| |
| // Pop the strongly connected components off the stack if |
| // this is a root vertex |
| if index == minIdx { |
| var scc []Vertex |
| for { |
| v2 := acct.pop() |
| scc = append(scc, v2) |
| if v2 == v { |
| break |
| } |
| } |
| |
| acct.SCC = append(acct.SCC, scc) |
| } |
| |
| return minIdx |
| } |
| |
| func min(a, b int) int { |
| if a <= b { |
| return a |
| } |
| return b |
| } |
| |
| // sccAcct is used ot pass around accounting information for |
| // the StronglyConnectedComponents algorithm |
| type sccAcct struct { |
| NextIndex int |
| VertexIndex map[Vertex]int |
| Stack []Vertex |
| SCC [][]Vertex |
| } |
| |
| // visit assigns an index and pushes a vertex onto the stack |
| func (s *sccAcct) visit(v Vertex) int { |
| idx := s.NextIndex |
| s.VertexIndex[v] = idx |
| s.NextIndex++ |
| s.push(v) |
| return idx |
| } |
| |
| // push adds a vertex to the stack |
| func (s *sccAcct) push(n Vertex) { |
| s.Stack = append(s.Stack, n) |
| } |
| |
| // pop removes a vertex from the stack |
| func (s *sccAcct) pop() Vertex { |
| n := len(s.Stack) |
| if n == 0 { |
| return nil |
| } |
| vertex := s.Stack[n-1] |
| s.Stack = s.Stack[:n-1] |
| return vertex |
| } |
| |
| // inStack checks if a vertex is in the stack |
| func (s *sccAcct) inStack(needle Vertex) bool { |
| for _, n := range s.Stack { |
| if n == needle { |
| return true |
| } |
| } |
| return false |
| } |