For a connected, directed graph, with starting node N0.
type State: { unvisited, visited, part_of_cycle }
reset(N0):
for all nodes N in graph N0:
state(N) = unvisited
identify_cycle_nodes_rec(N, T) is
match state(N)
case part_of_cycle:
return
case visited:
state(N) = part_of_cycle
for M in neighbors(N):
visit(M, part_of_cycle)
case unvisited:
state(N) = T
for M in neighbors(N):
visit(M, T)
identify_cycle_nodes(N):
reset(N)
identify_cycle_nodes(N, visited)
The idea here is to traverse the graph, painting it with two successive colors. Our initial paint is "visited". We paint unvisited nodes with our currently chosen paint. If we encounter a node we have already painted "visited", we switch our paintbrush to the "part_of_cycle" color and paint the node again, visiting its neighbors with that brush. Whenever we encounter a node painted "part_of_cycle", we terminate the recursion. The "part_of_cycle" color will flow into all the nodes that are part of a cycle.
I used this in a compiler's optimizer to identify all the basic blocks which are part of a cycle.