Skip to content
Home DSA Cycle Detection in Graphs — DFS, Union-Find, and Floyd's Algorithm

Cycle Detection in Graphs — DFS, Union-Find, and Floyd's Algorithm

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Graphs → Topic 10 of 17
Learn three cycle detection algorithms: DFS with coloring for directed graphs, Union-Find for undirected graphs, and Floyd's tortoise-and-hare for linked lists.
⚙️ Intermediate — basic DSA knowledge assumed
In this tutorial, you'll learn
Learn three cycle detection algorithms: DFS with coloring for directed graphs, Union-Find for undirected graphs, and Floyd's tortoise-and-hare for linked lists.
  • Back edges in DFS indicate cycles in directed graphs; gray nodes in the current path are back-edge targets.
  • Union-Find detects cycles in undirected graphs by checking if edge endpoints are already in the same component.
  • Floyd's tortoise-and-hare uses O(1) space for linked list cycle detection.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer

For undirected graphs: DFS marks nodes as visited; a cycle exists if you encounter an already-visited node that is not the parent. For directed graphs: track nodes in the current DFS path (recursion stack); a cycle exists if you encounter a node already in the recursion stack. Union-Find is an efficient alternative for undirected graphs.

What is Cycle Detection? — Plain English

A cycle in a graph is a path that starts and ends at the same node. Cycle detection is important in many algorithms: topological sort requires an acyclic graph (DAG), dependency resolution must detect circular dependencies, and deadlock detection in operating systems finds cycles in resource-allocation graphs. For directed graphs, we use DFS with three-color marking (white=unvisited, gray=in current path, black=done). For undirected graphs, Union-Find is simpler. For linked lists, Floyd's cycle detection (tortoise and hare) uses O(1) space.

How Cycle Detection Works — Step by Step

Directed graph cycle detection using DFS coloring: 1. Initialize all nodes as WHITE (unvisited). 2. For each WHITE node, start a DFS. 3. Mark node as GRAY (currently being explored) before visiting neighbors. 4. If we reach a GRAY neighbor, a back edge exists → cycle detected → return True. 5. After exploring all neighbors, mark node as BLACK (fully explored). 6. If DFS completes without finding a gray neighbor, no cycle through this path.

Undirected graph cycle detection using Union-Find: 1. For each edge (u,v), check if u and v are already in the same component (find(u) == find(v)). 2. If yes, adding this edge creates a cycle → return True. 3. If no, union the components and continue. 4. If all edges are processed without triggering step 2, no cycle exists.

Linked list cycle detection (Floyd's): 1. slow = fast = head. 2. Each step: slow moves 1, fast moves 2. 3. If fast or fast.next is None → no cycle. 4. If slow == fast → cycle exists.

Worked Example — Tracing Directed Graph Cycle Detection

Directed graph: 0→1→2→3→1 (cycle: 1→2→3→1). Edges: (0,1),(1,2),(2,3),(3,1).

DFS from 0: 1. Visit 0: color[0]=GRAY. Explore neighbor 1. 2. Visit 1: color[1]=GRAY. Explore neighbor 2. 3. Visit 2: color[2]=GRAY. Explore neighbor 3. 4. Visit 3: color[3]=GRAY. Explore neighbor 1. 5. Node 1 is GRAY → BACK EDGE found! Cycle detected. Return True.

Undirected example: edges (0,1),(1,2),(2,0). Union-Find: process (0,1): find(0)=0, find(1)=1, different → union. Process (1,2): different → union. Process (2,0): find(2)=0, find(0)=0, SAME → cycle detected!

Implementation

For directed graphs, maintain a color array (0=white,1=gray,2=black). DFS sets color to gray on entry and black on exit; finding a gray neighbor is a cycle. For undirected graphs, Union-Find checks connectivity before adding each edge. For linked lists, two pointers advance at different speeds — if they meet, a cycle exists. The DFS approach is O(V+E) time and O(V) space; Union-Find is O(E * alpha(V)) ≈ O(E) with path compression.

cycle_detection.py · PYTHON
1234567891011121314151617181920212223242526272829303132333435363738
# Directed graph cycle detection (DFS coloring)
def has_cycle_directed(num_v, adj):
    WHITE, GRAY, BLACK = 0, 1, 2
    color = [WHITE] * num_v

    def dfs(v):
        color[v] = GRAY
        for nb in adj.get(v, []):
            if color[nb] == GRAY:   # back edge → cycle
                return True
            if color[nb] == WHITE and dfs(nb):
                return True
        color[v] = BLACK
        return False

    return any(dfs(v) for v in range(num_v) if color[v] == WHITE)

# Test directed
adj_d = {0:[1], 1:[2], 2:[3], 3:[1]}  # cycle 1->2->3->1
print(has_cycle_directed(4, adj_d))    # True

# Undirected graph cycle detection (Union-Find)
def has_cycle_undirected(num_v, edges):
    parent = list(range(num_v))
    def find(x):
        while parent[x] != x:
            parent[x] = parent[parent[x]]
            x = parent[x]
        return x
    def union(a, b):
        ra, rb = find(a), find(b)
        if ra == rb: return True   # already connected → cycle
        parent[ra] = rb
        return False
    return any(union(u, v) for u, v in edges)

print(has_cycle_undirected(3, [(0,1),(1,2),(2,0)]))  # True
print(has_cycle_undirected(3, [(0,1),(1,2)]))         # False
▶ Output
True
True
False

🎯 Key Takeaways

  • Back edges in DFS indicate cycles in directed graphs; gray nodes in the current path are back-edge targets.
  • Union-Find detects cycles in undirected graphs by checking if edge endpoints are already in the same component.
  • Floyd's tortoise-and-hare uses O(1) space for linked list cycle detection.

Interview Questions on This Topic

  • QHow do you detect a cycle in a directed graph?
  • QWhat is the difference between a back edge and a cross edge?
  • QHow does Union-Find detect cycles?

Frequently Asked Questions

Why use three colors (white/gray/black) instead of just visited/unvisited for directed graphs?

Two colors cannot distinguish between a neighbor on the current DFS path (which indicates a cycle) and a neighbor that was visited via a different DFS path (which is fine). A back edge (gray→gray) is a cycle; a cross edge (gray→black) is not. Three colors are required to make this distinction.

Why doesn't the simple visited set work for undirected graphs in cycle detection?

For undirected graphs, each edge appears twice (u→v and v→u). When DFS is at node u having come from v, v is already visited. This is a 'backward edge' in undirected DFS but not a cycle. The fix: track the parent and skip the parent when checking neighbors.

How does Floyd's algorithm find the cycle start after detecting a cycle?

After slow meets fast inside the cycle, reset one pointer to the head. Advance both pointers one step at a time. The meeting point is the cycle start. Mathematical proof: if the distance from head to cycle start is F, and the cycle length is C, then slow has traveled F+k*C+m steps for some k. The reset pointer meets slow at the cycle start after F more steps.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousMinimum Spanning Tree — Kruskal and PrimNext →Bipartite Graph Check
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged