I'm writing a library which is configured using a recursive structure.
For the sake of this discussion I'm calling these graph structures a "tree" since there is a defined "root" node and each node can refer to more than "child". When properly configured no loops should exist. It differs a little from a tree because child node can be used in multiple places.
A A
/ \ / \
B C B C
/ \ / \ / \ \
D E F D E |
\ |
F
Both of these are acceptable despite the fact that E and F are used multiple times on multiple layers. Nodes can have multiple parents and multiple children but MUST NEVER be their own ancestor.
However
A
|
B
|
A
|
...
Is not acceptable because of the loop.
If my library was to be given a graph with a cycle in it then bad things would happen to the library so I am looking for a way to sanity check the input. I need to determine if recursing through this structure will terminate or if it will get stuck in an infinite loop. In effect I need to look for cycles in a directed graph.
There is a cycle in a graph only if there is a back edge present in the graph. Depth First Traversal can be used to detect a cycle in a Graph, DFS for a connected graph produces a tree.
A tree is a connected graph without cycles. That is, there is a path from any vertex to any other, but no path from a vertex to itself that does not traverse each edge on it an even number of times. Without edges, the empty graph has |V| connected components.
To test if your directed graph contains cycles (references to nodes already added to the tree) you can iterate trough the tree and add each node to a visited-list (or the hash of it if you rather prefer) and check each new node if it is in the list.
You can still use BFS to detect cycle in a Directed Graph, but in that case you also have to use Topological Sorting along with BFS. Please refer to the Topological Sort by BFS section of the article "Topological Sort: DFS, BFS and DAG". In this approach you won't need to find cross-edge in this approach.
While writing the question I've realised that this can be done with a modified version of the Hare and Tortoise algorithm.
The modification does require some additional memory where the original algorithm did not.
The basic modification to the algorithm is:
I'm posting an untested code example for this in C. I'll update it once its tested.
#define HAS_LOOP 1
#define DOES_NOT_HAVE_LOOP 0
// Tree nodes each have an array of children
struct TreeNode {
// some value, eg:
int value;
// child nodes:
struct TreeNode * nodes;
int nodeCount;
};
// These structures are used to form a single linked list on which Hair and Tortoise will be evaluated
struct LoopDetectionNode {
struct TreeNode * treeNode;
struct LoopDetectionNode * next;
};
static int hasLoopRecursive(struct LoopDetectionNode * hare, struct LoopDetectionNode * tortoise, int isOdd) {
struct LoopDetectionNode newHare = {
.next = NULL;
};
hare->next = &newHare;
if (isOdd) tortoise = tortoise->next;
isOdd = !isOdd;
for (int i = 0; i < hare->treeNode->nodeCount; i++) {
newHare.treeNode = hare->treeNode->nodes[i];
if (newHare.treeNode == tortoise.treeNode || hasLoopRecursive(&newHare, tortoise->next, isOdd) == HAS_LOOP) return HAS_LOOP;
}
return DOES_NOT_HAVE_LOOP;
}
int hasLoop(struct TreeNode * node) {
struct LoopDetectionNode hare = {
.next = NULL;
};
struct LoopDetectionNode tortoise = {
.next = &hare;
.treeNode = node;
};
for (int i = 0; i < node->nodeCount; i++) {
hare.treeNode = node->nodes[i];
if (hare.treeNode == node || hasLoopRecursive(hare, tortoise, 0) == HAS_LOOP) return HAS_LOOP;
}
return DOES_NOT_HAVE_LOOP;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With