Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect if an undirected graph has a cycle and output it using BFS or DFS

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.

like image 734
eltigre Avatar asked Jan 30 '15 20:01

eltigre


2 Answers

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.]

like image 60
j_random_hacker Avatar answered Nov 15 '22 05:11

j_random_hacker


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!

like image 27
bajuwa Avatar answered Nov 15 '22 05:11

bajuwa