Tarjan's strongly connected components algorithm: Difference between revisions

Content deleted Content added
Reverted edits by 141.85.150.221 (talk) (AV)
Stack invariant: ce: attempt to fix one gibberish sentence and removing a second
Tags: Mobile edit Mobile web edit
 
(29 intermediate revisions by 19 users not shown)
Line 1:
{{Short description|Graph algorithm}}
{{CS1 config|mode=cs2}}
{{Infobox algorithm
|class=
Line 11 ⟶ 13:
|complete=
}}
'''Tarjan's strongly connected components algorithm''' is an [[algorithm]] in [[graph theory]] for finding the [[strongly connected component]]s (SCCs) of a [[directed graph]]. It runs in [[linear time]], matching the time bound for alternative methods including [[Kosaraju's algorithm]] and the [[path-based strong component algorithm]]. The algorithm is named for its inventor, [[Robert Tarjan]].<ref name=Tarjan>{{citation|first=R. E.|last=Tarjan|author-link=Robert Tarjan|title=Depth-first search and linear graph algorithms|journal=[[SIAM Journal on Computing]]|volume=1|year=1972|issue=2|pages=146–160|doi=10.1137/0201010|citeseerx=10.1.1.327.8418|url=http://www.cs.ucsb.edu/~gilbert/cs240a/old/cs240aSpr2011/slides/TarjanDFS.pdf|access-date=2024-04-07|archive-date=2017-08-29|archive-url=https://web.archive.org/web/20170829214726/http://www.cs.ucsb.edu/~gilbert/cs240a/old/cs240aSpr2011/slides/TarjanDFS.pdf|url-status=bot: unknown}}</ref>
 
== Overview ==
 
The algorithm takes a [[directed graph]] as input, and produces a [[Partition of a set|partition]] of the graph's [[Vertex (graph theory)|vertices]] into the graph's strongly connected components. Each vertex of the graph appears in exactly one of the strongly connected components. Any vertex that is not on a directed cycle forms a strongly connected component all by itself: for example, aany vertex whose in-degree or out-degree is 0, or anyevery vertex of ana [[directed acyclic graph]].
 
The basic idea of the algorithm is this: a depth-first search (DFS) begins from an arbitrary start node (and subsequent depth-first searches are conducted on any nodes that have not yet been found). As usual with depth-first search, the search visits every node of the graph exactly once, decliningrefusing to revisit any node that has already been visited. Thus, the collection of search trees is a [[Spanning forest#Spanning forests|spanning forest]] of the graph. The strongly connected components will be recovered as certain subtrees of this forest. The roots of these subtrees are called the "roots" of the strongly connected components. Any node of a strongly connected component might serve as a root, if it happens to be the first node of a component that is discovered by search.
 
=== Stack invariant ===
 
Nodes are placed on a [[Stack (data structure)|stack]] in the order in which they are visited. When the depth-first search recursively visits a node <code>v</code> and its descendants, those nodes are not all necessarily popped from the stack when this recursive call returns. The crucial [[Invariant (computer science)|invariant property]] is that a node remains on the stack after it has been visited if and only if there exists a path in the input graph from it to some node earlier on the stack. In other words, it means that in the DFS a node would beis only removed from the DFS stack afterwhen all of its connected paths have been traversed. When the DFS will backtrack it would remove the nodes on a single path and return to the root in order to start a new path.
 
At the end of the call that visits <code>v</code> and its descendants, we know whether <code>v</code> itself has a path to any node earlier on the stack. If so, the call returns, leaving <code>v</code> on the stack to preserve the invariant. If not, then <code>v</code> must be the root of its strongly connected component, which consists of <code>v</code> together with any nodes later on the stack than <code>v</code> (such nodes all have paths back to <code>v</code> but not to any earlier node, because if they had paths to earlier nodes then <code>v</code> would also have paths to earlier nodes which is false). The connected component rooted at <code>v</code> is then popped from the stack and returned, again preserving the invariant.
 
=== Bookkeeping ===
Line 29 ⟶ 31:
Each node <code>v</code> is assigned a unique integer <code>v.index</code>, which numbers the nodes consecutively in the order in which they are discovered. It also maintains a value <code>v.lowlink</code> that represents the smallest index of any node on the stack known to be reachable from <code>v</code> through <code>v</code>'s DFS subtree, including <code>v</code> itself. Therefore <code>v</code> must be left on the stack if <code>v.lowlink < v.index</code>, whereas v must be removed as the root of a strongly connected component if <code>v.lowlink == v.index</code>. The value <code>v.lowlink</code> is computed during the depth-first search from <code>v</code>, as this finds the nodes that are reachable from <code>v</code>.
 
Note that theThe lowlink is different from the lowpoint, which is the smallest index reachable from <code>v</code> through any part of the graph.<ref name=Tarjan/>{{rp|156}}<ref>{{cite web |titlename=Lecture #19: Depth First Search and Strong Components |url=https:"CMU2018"//www.cs.cmu.edu/~15451-f18/lectures/lec19-DFS-strong-components.pdf |website=15-451/651: Algorithms Fall 2018 |publisher=Carnegie Mellon University |access-date=9 August 2021 |pages=7-8}}</ref>
 
== The algorithm in pseudocode ==
Line 37 ⟶ 39:
'''output:''' set of strongly connected components (sets of vertices)
''index'' := 0 Dragonu
''S'' := empty stack
'''for each''' ''v'' '''in''' ''V'' '''do'''
'''if''' ''v''.index is undefined '''then'''
strongconnect(''v'')
'''end if'''
'''end for'''
'''function''' strongconnect(''v'')
Line 62:
''// Successor w is in stack S and hence in the current SCC''
''// If ''w'' is not on stack, then (''v'', ''w'') is an edge pointing to an SCC already found and must be ignored
''// Note:See Thebelow regarding the next line may look odd - but is correct.''
''// It says w.index not w.lowlink; that is deliberate and from the original paper''
''v''.lowlink := min(''v''.lowlink, ''w''.index)
'''end if'''
'''end for'''
''// If v is a root node, pop the stack and generate an SCC''
Line 77 ⟶ 74:
'''while''' ''w'' ≠ ''v''
output the current strongly connected component
'''end if'''
'''end function'''
 
The <code>index</code> variable is the depth-first search node number counter. <code>S</code> is the node stack, which starts out empty and stores the history of nodes explored but not yet committed to a strongly connected component. Note that thisThis is not the normal depth-first search stack, as nodes are not popped as the search returns up the tree; they are only popped when an entire strongly connected component has been found.
 
The outermost loop searches each node that has not yet been visited, ensuring that nodes which are not reachable from the first node are still eventually traversed. The function <code>strongconnect</code> performs a single depth-first search of the graph, finding all successors from the node <code>v</code>, and reporting all strongly connected components of that subgraph.
Line 86 ⟶ 81:
When each node finishes recursing, if its lowlink is still set to its index, then it is the root node of a strongly connected component, formed by all of the nodes above it on the stack. The algorithm pops the stack up to and including the current node, and presents all of these nodes as a strongly connected component.
 
NoteIn that <code>Tarjan''v''.lowlinks := min(''v''.lowlinkpaper, ''w''.index) </code> is the correct way to update <code>''v.lowlink''</code> if <code>''w''</code> is on stack. Becausewhen <code>''w''</code> is on the stack already, <code>''(v, w)''.lowlink</code> is aupdated back-edge inwith the DFS tree and thereforeassignment <code>''wv''</code>.lowlink is:= not in the subtree of <code>min(''v''</code>.lowlink, Because <code>''v.lowlinkw''.index)</code>.<ref takesname=Tarjan/>{{rp|157}} intoA accountcommon nodesvariation reachableis onlyto throughinstead the nodes in the subtree ofuse <code>''v''</code>.lowlink we:= must stop at <code>min(''wv''</code>.lowlink, and use <code>''w.index''.lowlink)</code> instead of <code>''w.lowlink''</coderef>.{{cite conference
| last1 = Kordy | first1 = Piotr
| last2 = Langerak | first2 = Rom
| last3 = Mauw | first3 = Sjouke
| last4 = Polderman | first4 = Jan Willem
| editor1-last = Jones | editor1-first = Cliff B.
| editor2-last = Pihlajasaari | editor2-first = Pekka
| editor3-last = Sun | editor3-first = Jun
| contribution = A symbolic algorithm for the analysis of robust timed automata
| contribution-url = https://satoss.uni.lu/members/sjouke/papers/KLMP14.pdf
| doi = 10.1007/978-3-319-06410-9_25
| isbn = 978-3-319-06409-3
| pages = 351–366
| publisher = Springer
| series = Lecture Notes in Computer Science
| title = FM 2014: Formal Methods – 19th International Symposium, Singapore, May 12–16, 2014. Proceedings
| volume = 8442
| year = 2014}}</ref><ref>{{cite web |title=Lecture 19: Tarjan's Algorithm for Identifying Strongly Connected Components in the Dependency Graph |url=http://courses.cms.caltech.edu/cs130/lectures-2024wi/CS130-Wi2024-Lec19.pdf |website=CS130 Software Engineering |publisher=Caltech |date=Winter 2024}}</ref> This modified algorithm does not compute the lowlink numbers as Tarjan defined them, but the test <code>''v''.lowlink = ''v''.index</code> still identifies root nodes of strongly connected components, and therefore the overall algorithm remains valid.<ref name="CMU2018">{{cite web |title=Lecture #19: Depth First Search and Strong Components |url=https://www.cs.cmu.edu/~15451-f18/lectures/lec19-DFS-strong-components.pdf |website=15-451/651: Design & Analysis of Algorithms |publisher=Carnegie Mellon |date=1 November 2018}}</ref>
 
== Complexity ==
Line 92 ⟶ 104:
 
In order to achieve this complexity, the test for whether <code>w</code> is on the stack should be done in constant time.
This maycan be done, foras example,in bythe storingpseudocode above: store a flag on each node that indicates whether it is on the stack, and performing this test by examining the flag.
 
''Space Complexity'': The Tarjan procedure requires two words of supplementary data per vertex for the <code>index</code> and <code>lowlink</code> fields, along with one bit for <code>onStack</code> and another for determining when <code>index</code> is undefined. In addition, one word is required on each stack frame to hold <code>v</code> and another for the current position in the edge list. Finally, the worst-case size of the stack <code>S</code> must be <math>|V|</math> (i.e. when the graph is one giant component). This gives a final analysis of <math>O(|V|\cdot(2+5w))</math> where <math>w</math> is the machine word size. The variation of Nuutila and Soisalon-Soininen reduced this to <math>O(|V|\cdot(1+4w))</math> and, subsequently, that of Pearce requires only <math>O(|V|\cdot(1+3w))</math>.<ref>{{cite journal|last=Nuutila|first=Esko|title=On Finding the Strongly Connected Components in a Directed Graph|journal=Information Processing Letters|pages=9–14|volume=49|number=1|doi=10.1016/0020-0190(94)90047-7|year=1994}}</ref><ref>{{cite journal|last=Pearce|first=David|title=A Space Efficient Algorithm for Detecting Strongly Connected Components|journal=Information Processing Letters|pages=47–52|number=1|volume=116|doi=10.1016/j.ipl.2015.08.010}}</ref>