【问题标题】:Lowest Common Ancestor Algorithm最低共同祖先算法
【发布时间】:2014-03-31 00:01:28
【问题描述】:

所以我一直在研究实现最低共同祖先算法。我查看了许多不同的算法(主要是 Trajan 解决方案的变体或 RMQ 的变体)。

我使用的是非二叉树。我的树经常会在查询之间发生变化,因此预处理不一定是值得的。树的节点不应超过 50-75 个。我想知道是我应该打扰使用他们的算法还是坚持我自己的算法。

我的算法

myLCA(node1, node2) {
    parentNode := [ ]
    while (node1!=NULL) {
         parentNode.push(node1)
         node1 := node1.parent
    }
     while (node2!=NULL) {
         for i in parentNode.size {
             if (parentNode(i) == node2) {
                 return node2; 
             }
         }
         node2 := node2.parent
     }

}       

【问题讨论】:

  • 嗯,我不明白这是一个真正的问题。您希望收到什么类型的答案?

标签: algorithm tree traversal least-common-ancestor


【解决方案1】:

正如其他人所提到的,您的算法目前是二次的。对于像 50-75 个节点这样小的数据集,这可能不是问题,但无论如何,无需使用任何集合或哈希表即可将其更改为线性时间,只需记录每个节点到根的完整路径,然后从根返回并寻找第一个不同的节点。紧接在前的节点(这两个不同节点的共同父节点)就是 LCA:

linearLCA(node1, node2) {
    parentNode1 := [ ]
    while (node1!=NULL) {
         parentNode1.push(node1)
         node1 := node1.parent
    }
    parentNode2 := [ ]
    while (node2!=NULL) {
         parentNode2.push(node2)
         node2 := node2.parent
    }
    while (node1 == node2 && !isEmpty(parentNode1) && !isEmpty(parentNode2)) {
        oldNode := node1
        node1 := parentNode1.pop()
        node2 := parentNode2.pop()
    }
    if (node1 == node2) return node1    // One node is descended from the other
    else return oldNode                 // Neither is descended from the other
}

编辑 27/5/2012: 处理一个节点从另一个节点下降的情况,否则会导致尝试 pop() 一个空堆栈。感谢该死的指出这一点。 (我也意识到跟踪一个oldNode就足够了。)

【讨论】:

  • 修改:在 java 等语言中,pop() 会抛出 EmptyStackException,应该通过异常处理或在弹出之前检查两个堆栈的大小来处理。
  • @damned:实际上这不是必需的,因为node1node2 属于同一棵树这一事实保证了在最终的while 循环中会找到一个共同的祖先——两者都不是这些对pop() 的调用可能会失败。
  • 如果我们有一个节点是另一个节点的后代,则总是会发生异常。以一对父子为例。
  • @damned:你说的很对,我很抱歉。稍后我会更新以解决此问题。
  • 我认为它仍然不能正确处理一个节点是来自另一个节点。假设 list1 有 3 个元素,而 list2 有 2 个元素。运行两次后出现问题。
【解决方案2】:

对于这么小的树,我不会费心去实现更复杂的东西。您的解决方案看起来不错,尽管时间复杂度是根据树的高度平方的。如果您可以轻松实现Set(大多数语言都内置了它),那么可以将算法调整为,

  1. 从第一个节点遍历到根节点,收集一个集合中的所有节点
  2. 从第二个节点向上遍历到根节点并检查当前节点是否存在于该集合中。如果有,那就是共同祖先。

此外,该算法假设一个节点可以是它自己的祖先。否则,您将不得不稍微调整算法。考虑这个例子,

A
|
B
|
C

当试图找到 B 和 C 的最低共同祖先时,此算法将报告 B,这可能是也可能不是真的,具体取决于您如何定义祖先。

【讨论】:

    【解决方案3】:

    在不查看任一算法的细节的情况下,我建议查看该算法的效率对您的整个应用程序的重要性,以及实现另一个算法需要多少努力。

    在您的应用程序的正常(或压力)操作中,该算法将运行多少次?它会导致用户等待比必要的时间更长吗?其他不同数量级的算法是否比你的更快? (熟悉算法的人可以给你更详细的答案。)

    我认为不值得优化一点代码,除非你会看到可观的结果(有些人强烈认为过早优化是the root of all evil

    【讨论】:

      【解决方案4】:

      您的算法是二次的,但很容易变成线性的。

      只需对parentNode 使用哈希表(即设置),而不是列表。 因此检查一个节点是否在parentNode 将是O(1) 而不是O(n)

      【讨论】:

        【解决方案5】:

        我刚刚写了一篇博客文章,讲述了我必须如何为这个问题实现自己的算法,但扩展到一组任意长度的节点。 您可以在此处找到它(通过逐步图解说明其工作原理)

        http://bio4j.com/blog/2012/02/finding-the-lowest-common-ancestor-of-a-set-of-ncbi-taxonomy-nodes-with-bio4j/

        干杯,

        巴勃罗

        【讨论】:

        • 直接链接对我不起作用,但是如果你用谷歌搜索它,你会得到它。
        【解决方案6】:

        我有一个简单的解决方案 对两个元素进行排序,最低为左,最高为右 访问根 def 递归(根) 如果 root.empty 则返回 nil? 如果左 = 根 返回根 elsif 左

        所以这将检查每次遍历 平均和最差的问题在 O(log n) 时间内解决,O(log

        【讨论】:

          猜你喜欢
          • 2014-05-24
          • 1970-01-01
          • 2013-10-01
          • 2011-05-03
          • 1970-01-01
          • 1970-01-01
          • 2011-07-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多