cycle detection ( generic )

the general way to do is use a visited enum with 3 states.

  • 0 : new
  • 1 : exploring
  • 2 : explored
visited[...all nodes] = new
 
def dfs(u):
    visited[u] = exploring
    for v in neighbors(u):
        visited(v) is:
            exploring => CYCLE
            new => dfs to this
            explored => skip
    mark explored

(cycle start, cycle end) would be the (v, u) tuple when in CYCLE branch. idea being, we have v as exploring (in the dfs stack) and now see another edge that can lead us there.

just rem that start is obv from what was explored already v

now to get the cycle path; two ways

  • you can obv make a “parent” array, go from end to start and reverse
  • OR and the cleaner way is to keep storing the current node stack ( just like backtracking in a vec )
if found cycle:
    cycle = nodes[ find cycle start... ]

you don’t even need to reverse this way

simplification when undirected

you can get down to just new and exploring states while maintaining a “prev” var.

idea being when I’m exploring neighbors

if visited(neighbor) and neighbor != parent:
    then cycle is path from neighbor to current + this edge from cur to neighbor (to close the loop)

the simplification depends on whether the node being already visited implies that a back edge exists. it may or may not depending on if the node is STILL in the dfs stack

all cycles?

  • directed: johnson’s algo ( uses SCCs ), skipping for now
  • undirected: nothing polynomial time really, you do an all cycle dfs basically

a counting all cycles optimisation

using a dp you can get a better upper bound of exponential compared to of dfs

we solve for EACH start := s so this below is all in another loop

note that dp is on number of paths not cycles

base:
f(1<<s,s) = 1

transition
for cnt = f(mask,u)
    for v:= neigh(u) and not in mask:
        f(mask | v, v) += cnt

to actually get cycles:

for mask,u:
    if |mask| >= 3 and edge(u -> s):
        ans += f(mask,u)

now to avoid duplicates, when you count you would want to force this invariant: “s is the smallest in mask”

for undirected graphs even after ^ you would have fwd and reverse cycles from same s