【问题标题】:Detecting loops in tree structures (graphs)检测树结构(图)中的循环
【发布时间】:2016-10-26 03:53:44
【问题描述】:

我正在编写一个使用递归结构配置的库。

为了便于讨论,我将这些图形结构称为“树”,因为有一个已定义的“根”节点,并且每个节点都可以引用多个“子节点”。正确配置后,不应存在环路。它与树略有不同,因为子节点可以在多个地方使用。

      A                  A
     / \                / \
    B   C              B   C
   / \ / \            / \   \
  D   E   F          D   E  |
                          \ |
                            F

尽管 E 和 F 在多个层上多次使用,但这些都是可以接受的。节点可以有多个父节点和多个子节点,但绝不能是它们自己的祖先。

然而

A
|
B
|
A
|
...

由于循环不可接受。

如果要给我的库提供一个带有循环的图表,那么库会发生坏事,所以我正在寻找一种方法来健全地检查输入。我需要确定通过这个结构的递归是否会终止,或者它是否会陷入无限循环。实际上,我需要在有向图中寻找循环。

【问题讨论】:

  • 您的要求就像 OO 中的继承,但有多个父级,我的意思是它与在图中检测循环略有不同。
  • 检测多继承的循环或接口继承的循环检测是一样的。我之前没有注意到这一点。如果您对编译器如何检测到这一点有任何了解,那将会很有趣。

标签: algorithm tree infinite-loop graph-algorithm


【解决方案1】:

在写这个问题时,我意识到这可以通过Hare and Tortoise algorithm 的修改版本来完成。

修改确实需要一些额外的内存,而原始算法不需要。

算法的基本修改是:

  • 兔子首先遍历树的深度,而不是遍历列表。
  • “Hare”维护指向图形节点的指针列表(例如:链表)。它在递归时将一个节点添加到此列表中,并在递归时弹出该节点。
  • 当兔子在路径(列表)中添加一个节点使其元素个数为偶数时,乌龟会向前迈出一步。
  • 当兔子从路径(列表)中删除一个节点使其成为奇数时,乌龟会后退一步。
  • 兔子和乌龟节点每次递归时都会进行比较,如果两者相等则找到循环。这会导致算法停止
  • 如果算法遍历整个树,则不会出现循环。

我在 C 中为此发布了一个未经测试的代码示例。一旦测试,我将对其进行更新。

#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(node->path, &newHare, 0) == 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;
}

【讨论】:

  • node-&gt;path 中的 hasLoopRecursive 应该是 tortoise-&gt;next 吗?否则,这是很好的代码。
  • node-&gt;path in hasLoopRecursive 确实应该是 tortoise-&gt;next 并且应该是第二个参数,而不是第一个。第一个参数应该是newHare。最后一个参数也应该是 isOdd 而不是 0。完整的行如下所示:if (newHare.treeNode == tortoise.treeNode || hasLoopRecursive(&amp;newHare, tortoise-&gt;next, isOdd) == HAS_LOOP) return HAS_LOOP;
  • @MurariuSerban 我写这篇文章已经有一段时间了。如果我在这里写的内容有错别字,请随时提供edit,我会进行审查。
猜你喜欢
  • 2018-11-19
  • 1970-01-01
  • 2020-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多