The other question on this only answered how to detect a cycle, not also output it. So, I'd like to write an algorithm run BFS or DFS in O(V + E) time (V=vertices, E=edges), on an undirected graph, and output the cycle if it has one.
What I know so far is how BFS/DFS works and that you can detect a cycle using BFS if you visit a node that already has been marked as visited.
To detect and output a cycle with DFS, just mark each vertex as you get to it; if any child of the current vertex is marked, you know you have a cycle involving that child. Furthermore you know that that child vertex is the first vertex belonging to this particular cycle that was encountered by the DFS, and that every move in the DFS since it first encountered that vertex (i.e. every recursive call since then that hasn't yet returned) has visited another vertex in the cycle. The only information you need to pass back up the call stack is this child vertex, or a special value indicating that no cycle was found. You can pass this back as a return value:
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
}
After calling dfs(r, nil)
for some vertex r
(and any non-vertex value nil
), cycleVertices
will be populated with a cycle if one was found.
[EDIT: As pointed out by Juan Lopes, unmarking vertices is not necessary, and is possibly confusing; but, interestingly, doesn't affect the time complexity for undirected graphs.]
If you're using DFS, you can do it recursively by printing out the name of the node depending on whether the visited node is already visited:
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
By the end of it, cycle will contain a cycle that was found in the graph, otherwise it will be empty. Also note that the order that the cycle is shown will be dependant on how you 'push' to the cycle (push to back will print backwards, push to front will print 'in order')
Edit: Many thanks to @j_random_hacker for helping me debug my pseudo-code!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With