关于此的另一个问题仅回答了如何检测循环,而不是输出循环。因此,我想在无向图上编写一个在O(V + E)时间(V =顶点,E =边缘)运行BFS或DFS的算法,如果有一个循环,则输出循环。
到目前为止,我所知道的是BFS / DFS如何工作,如果您访问已经标记为已访问的节点,则可以使用BFS检测周期。
答案 0 :(得分:4)
要使用DFS检测并输出循环,只需在到达时标记每个顶点;如果标记了当前顶点的任何子节点,则表示您有一个涉及该子节点的循环。此外,你知道那个子顶点是DFS遇到的属于这个特定周期的第一个顶点,并且自从它首次遇到该顶点以来DFS中的每一个移动(即从那时起的每个递归调用还没有返回)已经访问了循环中的另一个顶点。传递回调用堆栈所需的唯一信息是此子顶点,或指示未找到循环的特殊值。您可以将其作为返回值传递回来:
dfs(v, p) {
marked[v] = true
For each neighbour u of v:
If u != p: # I.e. we ignore the edge from our parent p
If marked[u]:
Append v to cycleVertices
Return u # Cycle!
Else:
result = dfs(u, v)
If result == FINISHED:
# Some descendant found a cycle; now we're just exiting
Return FINISHED
Else if result != NOCYCLE:
# We are in a cycle whose "top" vertex is result.
Append v to cycleVertices
If result == v:
return FINISHED # This was the "top" cycle vertex
Else:
return result # Pass back up
marked[v] = false # Not necessary, but (oddly?) not harmful either ;)
Return NOCYCLE
}
为某些顶点dfs(r, nil)
(以及任何非顶点值r
)调用nil
后,cycleVertices
将填充一个循环(如果找到一个)。
[编辑:正如胡安·洛佩斯所指出的那样,没有标记的顶点是不必要的,并且可能令人困惑;但是,有趣的是,不会影响无向图的时间复杂度。]
答案 1 :(得分:1)
如果您正在使用DFS,则可以通过打印出节点名称来递归地执行此操作,具体取决于是否已访问过访问过的节点:
define function DFSVisit(node, cycle):
if node.visited is true:
push node.name to cycle
return true
else
set node.visited to true
for each child of node:
if DFSVisit(child, cycle) is true:
set foundCycle to true
break out of for loop
if foundCycle is true:
if (cycle.length <= 1 or cycle[first] != cycle[last]):
push node.name to cycle
return true
else
return false
在它结束时,循环将包含在图中找到的循环,否则它将为空。另请注意,显示循环的顺序取决于您如何推动&#39;循环(按下将向后打印,推到前面将打印&#39;按顺序排列&#39;)
编辑:非常感谢@j_random_hacker帮助我调试我的伪代码!