在深度优先搜索期间检测家谱图中的循环

时间:2009-03-07 03:10:27

标签: .net algorithm binary-tree genealogy

我正在递归地加载马谱系数据。 对于一些错误的数据集,我的递归永远不会停止......这是因为数据中有循环。

如何检测这些循环以停止重复?

我想到的是,在重复播放时,所有“访问过的”马都会保持哈希表。 但这会发现一些误报,因为一匹马可以在树上两次。

不可能发生的事情是,一匹马出现​​在自己的父亲或祖父或曾祖父的身上。

6 个答案:

答案 0 :(得分:6)

伪代码:

void ProcessTree(GenTreeNode currentNode, Stack<GenTreeNode> seen)
{
   if(seen.Contains(currentNode)) return;
   // Or, do whatever needs to be done when a cycle is detected

   ProcessHorse(currentNode.Horse); // Or whatever processing you need

   seen.Push(currentNode);

   foreach(GenTreeNode childNode in currentNode.Nodes)
   {
      ProcessTree(childNode, seen);
   }

   seen.Pop();
}

基本思想是保留我们在前往当前节点的过程中已经看到的所有节点的列表;如果回到我们已经经历的节点,那么你知道我们已经形成了一个循环(我们应该跳过这个值,或者做任何需要做的事情)

答案 1 :(得分:2)

维护一堆通向树根的所有元素。

每次向前推进树时,扫描堆栈中的子元素。如果你找到一个匹配,那么你已经发现了一个循环,应该跳过那个孩子。否则,将孩子推入堆叠并继续。每当你回溯树时,将一个元素从堆栈中弹出并丢弃。

(在谱系数据的情况下,树中的“子”节点可能是“父”节点的生物父节点。)

答案 2 :(得分:2)

这听起来像你可以最终应用那个采访琐事问题的情况:只使用O(1)内存在链表中找到一个循环。

在这种情况下,您的“链接列表”是您枚举的元素序列。使用两个枚举器,以半速运行一个,如果快速运行速度慢,那么就有一个循环。这也将是O(n)时间而不是检查“看到”列表所需的O(n ^ 2)时间。缺点是你只能在一些节点被多次处理后才发现循环。

在示例中,我使用更简单的“drop marker”方法替换了“半速”方法。

class GenTreeNode {
    ...

    ///<summary>Wraps an the enumeration of linked data structures such as trees and linked lists with a check for cycles.</summary>
    private static IEnumerable<T> CheckedEnumerable<T>(IEnumerable<T> sub_enumerable) {
        long cur_track_count = 0;
        long high_track_count = 1;
        T post = default(T);
        foreach (var e in sub_enumerable) {
            yield return e;
            if (++cur_track_count >= high_track_count) {
                post = e;
                high_track_count *= 2;
                cur_track_count = 0;
            } else if (object.ReferenceEquals(e, post)) {
                throw new Exception("Infinite Loop");
            }
        }
    }

    ...

    ///<summary>Enumerates the tree's nodes, assuming no cycles</summary>
    private IEnumerable<GenTreeNode> tree_nodes_unchecked() {
        yield return this;
        foreach (var child in this.nodes)
            foreach (var e in child.tree_nodes_unchecked())
                yield return e;
    }
    ///<summary>Enumerates the tree's nodes, checking for cycles</summary>
    public IEnumerable<GenTreeNode> tree_nodes()
    {
        return CheckedEnumerable(tree_nodes_unchecked());
    }

    ...

    void ProcessTree() {
        foreach (var node in tree_nodes())
            proceess(node);
    }
}

答案 3 :(得分:1)

检测此问题的一种非常简单的方法是检查约束本身:

  

不可能发生的事情是,一匹马看起来像是父亲或祖父或者是自己的祖父。

每当您在树中插入一个节点时,将树遍历到根目录以确保该马不存在于任何类型的父级中。

为了加快速度,您可以将散列表与每个节点相关联,您可以在其中缓存此类查找的答案。然后,下次在该节点下插入马时,您不必搜索整个路径。

答案 4 :(得分:0)

如果您跟踪节点而不是马匹,您的哈希表解决方案应该有效。只要确保每次读新马时都要创建一个新节点,即使值/ horse与前一个节点的值/马相同。

答案 5 :(得分:0)

您正在处理directed acyclic graph,而不是树。不应该有任何周期,因为马的后代不能也是它的祖先。

了解这一点,您应该应用特定于有向无环图的代码技术。