The algorithm described on Wikipedia looks easy enough to make non-recursive with an explicit stack. Starting from this (included here for reference, in case of a change in Wikipedia):
Input: Graph G = (V, E) index = 0 // DFS node number counter S = empty // An empty stack of node for all v in V do if (v.index is undefined) // Start a DFS at each node tarjan(v) // we haven't visited yet procedure tarjan(v) v.index = index // Set the depth index for v v.lowlink = index // SHOULD BE v.lowlink = MAX_INT? index = index + 1 S.push(v) // Push v on the stack for all (v, v') in E do // Consider successors of v if (v'.index is undefined) // Was successor v' visited? tarjan(v') // Recurse v.lowlink = min(v.lowlink, v'.lowlink) else if (v' is in S) // Was successor v' in stack S? v.lowlink = min(v.lowlink, v'.index ) // v' is in stack but it isn't in the dfs tree if (v.lowlink == v.index) // Is v the root of an SCC? print "SCC:" repeat v' = S.pop print v' until (v' == v)
Step 1: Remove loops containing recursion by adding labels and gotos. This is necessary in order to make loop variables clear, convenient, and recoverable (necessary during recursion-modeling with stacks). The label must be added after tarjan() returns, since we will get to it in a moment.
procedure tarjan(v) v.index = index // Set the depth index for v v.lowlink = index // SHOULD BE v.lowlink = MAX_INT? index = index + 1 S.push(v) // Push v on the stack succ = all (v, v') in E // Consider successors of v succIndex = 0 // presume succ is 0-based loop_top: if succIndex >= Length(succ) goto skip_loop v' = succ[succIndex] if (v'.index is undefined) // Was successor v' visited? tarjan(v') // Recurse recursion_returned: v.lowlink = min(v.lowlink, v'.lowlink) else if (v' is in S) // Was successor v' in stack S? v.lowlink = min(v.lowlink, v'.index ) // v' is in stack but it isn't in the dfs tree succIndex = succIndex + 1 goto loop_top skip_loop: if (v.lowlink == v.index) // Is v the root of an SCC? print "SCC:" repeat v' = S.pop print v' until (v' == v)
Step 2: Imagine a stack that contains all the appropriate state to store our position and calculate in a loop at any point where we can return from recursion, or start at the top of the loop.
Stack:
T = empty
state is an enumeration ( TopState , ReturnedState ) encoding the location in the procedure. Here the procedure is rewritten to use this stack and state, not recursion:
procedure tarjan(v) while (T is not empty) do (v, v', succ, succIndex, state) = T.pop case state of TopState: goto top ReturnedState: goto recursion_returned end case top: v.index = index // Set the depth index for v v.lowlink = index // SHOULD BE v.lowlink = MAX_INT? index = index + 1 S.push(v) // Push v on the stack succ = all (v, v') in E // Consider successors of v succIndex = 0 // presume succ is 0-based loop_top: if succIndex >= Length(succ) goto skip_loop v' = succ[succIndex] if (v'.index is undefined) // Was successor v' visited? // instead of recursing, set up state for return and top and iterate T.push(v, v', succ, succIndex, ReturnedState) // this is where we return to T.push(v', empty, empty, empty, TopState) // but this is where we go first continue // continue the while loop at top recursion_returned: v.lowlink = min(v.lowlink, v'.lowlink) else if (v' is in S) // Was successor v' in stack S? v.lowlink = min(v.lowlink, v'.index ) // v' is in stack but it isn't in the dfs tree succIndex = succIndex + 1 goto loop_top skip_loop: if (v.lowlink == v.index) // Is v the root of an SCC? print "SCC:" repeat v' = S.pop print v' until (v' == v)
Step 3: Finally, we need to make sure that the input conditions are correct for the top-level code that calls tarjan. This is easy to do with an initial click:
procedure tarjan(v) T.push(v, empty, empty, empty, TopState) while (T is not empty) do (v, v', succ, succIndex, state) = T.pop case state of TopState: goto top ReturnedState: goto recursion_returned end case top: v.index = index // Set the depth index for v v.lowlink = index // SHOULD BE v.lowlink = MAX_INT? index = index + 1 S.push(v) // Push v on the stack succ = all (v, v') in E // Consider successors of v succIndex = 0 // presume succ is 0-based loop_top: if succIndex >= Length(succ) goto skip_loop v' = succ[succIndex] if (v'.index is undefined) // Was successor v' visited? // instead of recursing, set up state for return and top and iterate T.push(v, v', succ, succIndex, ReturnedState) // this is where we return to T.push(v', empty, empty, empty, TopState) // but this is where we go first continue // continue the while loop at top recursion_returned: v.lowlink = min(v.lowlink, v'.lowlink) else if (v' is in S) // Was successor v' in stack S? v.lowlink = min(v.lowlink, v'.index ) // v' is in stack but it isn't in the dfs tree succIndex = succIndex + 1 goto loop_top skip_loop: if (v.lowlink == v.index) // Is v the root of an SCC? print "SCC:" repeat v' = S.pop print v' until (v' == v)
You can also do this with a jump, immediately jumping to top . The code can be further cleaned up, possibly converted to use a while or repeat loop to eliminate some of gotos, etc., but the above should be at least functionally equivalent, excluding explicit recursion.