terraform/digraph/tarjan.go

112 lines
2.4 KiB
Go

package digraph
// sccAcct is used ot pass around accounting information for
// the StronglyConnectedComponents algorithm
type sccAcct struct {
ExcludeSingle bool
NextIndex int
NodeIndex map[Node]int
Stack []Node
SCC [][]Node
}
// visit assigns an index and pushes a node onto the stack
func (s *sccAcct) visit(n Node) int {
idx := s.NextIndex
s.NodeIndex[n] = idx
s.NextIndex++
s.push(n)
return idx
}
// push adds a node to the stack
func (s *sccAcct) push(n Node) {
s.Stack = append(s.Stack, n)
}
// pop removes a node from the stack
func (s *sccAcct) pop() Node {
n := len(s.Stack)
if n == 0 {
return nil
}
node := s.Stack[n-1]
s.Stack = s.Stack[:n-1]
return node
}
// inStack checks if a node is in the stack
func (s *sccAcct) inStack(needle Node) bool {
for _, n := range s.Stack {
if n == needle {
return true
}
}
return false
}
// StronglyConnectedComponents implements Tarjan's algorithm to
// find all the strongly connected components in a graph. This can
// be used to detected any cycles in a graph, as well as which nodes
// partipate in those cycles. excludeSingle is used to exclude strongly
// connected components of size one.
func StronglyConnectedComponents(nodes []Node, excludeSingle bool) [][]Node {
acct := sccAcct{
ExcludeSingle: excludeSingle,
NextIndex: 1,
NodeIndex: make(map[Node]int, len(nodes)),
}
for _, node := range nodes {
// Recurse on any non-visited nodes
if acct.NodeIndex[node] == 0 {
stronglyConnected(&acct, node)
}
}
return acct.SCC
}
func stronglyConnected(acct *sccAcct, node Node) int {
// Initial node visit
index := acct.visit(node)
minIdx := index
for _, edge := range node.Edges() {
target := edge.Tail()
targetIdx := acct.NodeIndex[target]
// Recurse on successor if not yet visited
if targetIdx == 0 {
minIdx = min(minIdx, stronglyConnected(acct, target))
} else if acct.inStack(target) {
// Check if the node is in the stack
minIdx = min(minIdx, targetIdx)
}
}
// Pop the strongly connected components off the stack if
// this is a root node
if index == minIdx {
var scc []Node
for {
n := acct.pop()
scc = append(scc, n)
if n == node {
break
}
}
if !(acct.ExcludeSingle && len(scc) == 1) {
acct.SCC = append(acct.SCC, scc)
}
}
return minIdx
}
func min(a, b int) int {
if a <= b {
return a
}
return b
}