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  }