<p>迭代DFS本身并不复杂,如<a href="https://en.wikipedia.org/wiki/Depth-first_search#Pseudocode" rel="nofollow noreferrer" title="Wikipedia">Wikipedia</a>所示。然而,计算每个节点的完成时间需要对算法进行一些调整。我们只在第二次遇到节点时将其从堆栈中弹出。在</p>
<p>下面是我的实现,我觉得它可以更清楚地说明发生了什么:</p>
<pre><code>step = 0 # time counter
def dfs_visit(g, v):
"""Run iterative DFS from node V"""
global step
total = 0
stack = [v] # create stack with starting vertex
while stack: # while stack is not empty
step += 1
v = stack[-1] # peek top of stack
if v.color: # if already seen
v = stack.pop() # done with this node, pop it from stack
if v.color == 1: # if GRAY, finish this node
v.time_finish = step
v.color = 2 # BLACK, done
else: # seen for first time
v.color = 1 # GRAY: discovered
v.time_discover = step
total += 1
for w in v.child: # for all neighbor (v, w)
if not w.color: # if not seen
stack.append(w)
return total
def dfs(g):
"""Run DFS on graph"""
global step
step = 0 # reset step counter
for k, v in g.nodes.items():
if not v.color:
dfs_visit(g, v)
</code></pre>
<p>我遵循<a href="https://mitpress.mit.edu/books/introduction-algorithms" rel="nofollow noreferrer" title="CLR Algorithm Book">CLR Algorithm Book</a>的约定,并在DFS搜索期间使用节点着色来指定其状态。我觉得这比使用单独的列表来跟踪节点状态更容易理解。在</p>
<p>所有节点以<strong>白色开始</strong>。当在搜索过程中发现它时,它被标记为<strong>灰色</strong>。当我们完成它时,它被标记为<strong>黑色</strong>。在</p>
<p>在while循环中,如果一个节点是<em>白色的</em>,我们将其保留在堆栈中,并将其颜色更改为<em>灰色</em>。如果它是<em>灰色</em>我们将其颜色更改为<em>黑色</em>,并设置其完成时间。如果它是黑色的,我们就忽略它。在</p>
<p>堆栈上的节点有可能是黑色的(即使在将其添加到堆栈之前进行了着色检查)。一个白色节点可以两次添加到堆栈中(通过两个不同的邻居)。最终会变成黑色。当我们到达堆栈上的第二个实例时,我们需要确保不会更改它已经设置的完成时间。在</p>
<p>以下是一些附加的支持代码:</p>
^{pr2}$
<p>要在反向图上运行DFS,可以在处理edges文件时为每个节点构建类似于子列表的父列表,并在DFS_visit()中使用父列表而不是子列表。在</p>
<p>要在SCC计算的最后一部分减少完成时间来处理节点,可以构建节点的最大堆,并在dfs_visit()中使用该最大堆,而不是简单的子列表。在</p>
<pre><code> while g.max_heap:
v = heapq.heappop(g.max_heap)[1]
if not v.color:
size = dfs_visit(g, v)
scc_size.append(size)
</code></pre>