You can always simply flip some edges to get an acyclic graph from a cyclic one (graph G with vertices V and edges E):
input: G(V,E), n = |V|
visited <- empty set
queue <- empty queue
for each node in V
// skip visited nodes
if visited.find(node)
continue
// push a dummy edge (node is unvisited)
queue.push(edge(inf, node))
while !queue.empty()
edge <- queue.pop()
if visited.find(edge.stop)
// potential cycle detected
if edge.start != edge.stop
// eliminate loops, if any
E.flip(edge)
else
visited.insert(edge.stop)
for each outgoing edge e at edge.stop
queue.push(e)
Depending on the queue you use you get different behaviour:
- a stack (LIFO queue) results in depth-first traversal
- a FIFO queue results in breadth-first traversal
- a priority queue results in a DAG containing its spanning tree(s)
There's a caviat in the above code: potential cycle detected. Imagine a graph with vertices A, B, C
and edges A->B, A->C, C->B
. The above snippet detects a potential cycle when processing C->B
last. If you want to disambiguate valid edges from edges introducing cycles at that point you need to show that there are no paths from B
to C
yet. This is a much harder task and there are some good answers (and hints) in this answer here: basically you'd need to perform another graph traversal to detect (or exclude) such a conflicting path.