【问题标题】:Finding the common ancestor in a binary tree在二叉树中找到共同的祖先
【发布时间】:2011-09-04 17:23:30
【问题描述】:

在一次采访中向我提出了这个问题:我有一棵二叉树,我必须在给定二叉树的两个随机节点的情况下找到共同的祖先(父)。我还得到了一个指向根节点的指针。


我的回答是:

分别遍历两个节点的树,直到到达预期的节点。 遍历时并行将元素和下一个地址存储在链表中。然后我们有两个链表。所以尝试比较两个链表,两个链表中的最后一个公共节点是父节点。

我认为这个解决方案是正确的,如果我错了,请纠正我。 如果这个解决方案是正确的,我是否知道这是该任务唯一更好的解决方案,还是有比这更好的解决方案!

【问题讨论】:

  • @close voter:请添加评论,说明您为什么投票结束?
  • 你有指向父节点的指针吗?
  • 我有一个指向根节点的指针。
  • 在每个节点中?这有点奇怪。
  • 我必须将三个参数传递给我的 function.root 节点、node1、node2。我不知道这有什么奇怪的?

标签: c algorithm binary-tree binary-search-tree


【解决方案1】:

也许是愚蠢的做法:

生成从每个节点到根的路径,将其存储为“L”和“R”的字符串。

反转这些字符串。取最长的公共前缀 - 你现在有了共同祖先的路径。

【讨论】:

    【解决方案2】:

    在两个随机节点上设置一个指针。 通过遍历到顶部并计算与根节点的距离来找到每个节点的深度。 然后再次在两个节点上设置指针。对于更深的节点,向上遍历直到两个指针处于相同的深度。 然后向上遍历两个节点,直到指针指向同一个节点。那是祖先节点。

    “向上遍历”是指将指针移动到当前节点的父节点。

    编辑澄清:关键思想是当两个节点处于相同深度时,只需简单的遍历就可以很快找到共同的父节点。因此,您爬上较低的那个,直到两者处于相同的深度,然后您再向上遍历。 抱歉,我真的不懂 C,否则我会编写代码,但该算法应该可以回答您的问题。

    再次编辑:我的方法在 O(log(n)) 时间和 O(1) 内存中运行。

    另一个编辑: O(log(n)) 在平衡树中。对于不平衡的树,最坏情况的性能是 O(n)。谢谢@DaveCahill

    【讨论】:

    • 遍历树不计入时间复杂度计算吗?我原以为计算深度需要 O(n),其中 n 是节点深度,使得算法 O(n) 或线性时间。我似乎找不到讨论这一点的好资源,所以如果你有的话我会很感兴趣。
    • @DaveCahill 你说得对,但我将 n 作为元素的数量,而不是树的深度。考虑到这一点,它只是 O(log(n))
    • Gotcha,对平衡二叉树很有意义。我猜对于不平衡的树,它会是 O(n),因为 Big-O 表示法描述了最坏的情况。在不平衡二叉树的最坏情况下(根的左节点一直向下只有左子节点,右节点只有右子节点),该算法最终会访问所有 n 个元素两次。
    【解决方案3】:

    我认为您可以同时搜索两个节点;搜索分歧的点是共同祖先。

    commonAncestor tree a b:
      value := <value of node 'tree'>
      if (a < value) && (b < value)
      then commonAncestor (left tree) a b
      else if (a > value) && (b > value)
      then commonAncestor (right tree) a b
      else tree
    

    有趣的是,这种方法可以扩展到两个以上的节点(检查它们是否都在 tree 的左侧,等等)

    【讨论】:

      【解决方案4】:

      进行级别顺序遍历,对于遇到的每个节点,我们都会检查它的子节点。如果它们是提供的随机节点,则找到祖先节点。

      EDIT1:

      这是一个大纲

      struct _node {
         my_type data;
         struct _node *left;
         struct _node *right;
      }
      
      q = queue_create ();
      queue_insert (q, head);
      temp = head;
      while (!empty (q))
      {
          temp = queue_remove (q);
       if (
            (temp->left == my_random_node_1) && (head->right == my_random_node_2) ||
            (temp->left == my_random_node_2) && (head->right == my_random_node_1)
          )
          {
             /* temp is the common parent of the two target notes */
             /* Do stuffs you need to do */
          }
      
          /* Enqueue the childs, so that in successive iterations we can
           * check them, by taking out from the queue
           */
          push (q, temp->left);
          push (q, temp->right);
      }
      

      更新

      之前的算法只会找到共同的父母(直系祖先),因此如果两个随机选择的节点不是共同父母的孩子,则不会找到答案。

      以下算法将找到共同的祖先,而不仅仅是父母。

      我认为以下算法会起作用:

      对二叉树进行后序遍历,找到随机节点1r1,如果找到则在状态变量中将其标记为状态一,并继续寻找对于第二个节点,如果找到则将状态变量更新为状态二,并停止搜索更多并返回。状态变量应该由每个节点传递给它的父节点(递归地)。 状态二中第一个遇到状态变量的节点是共同祖先。

      算法的实现如下:

      int postorder (node *p, int r1, int r2)
      {
        int x = 0; /* The state variable */
        if (p->data == TERMINAL_VAL)
          return x;
      
        /* 0x01 | 0x02 = 0x03 threfore 
         * state one is when x = 0x01 or x = 0x02
         * state two is when x = 0x03
         */
        if (p->data == r1)
          x |= 0x01;
        else if (p->data == r2)
          x |= 0x02;
      
        /* if we have x in state two, no need to search more
         */
        if (x != 0x03)
          x |= postorder (p->left, r1, r2);
        if (x != 0x03)
          x |= postorder (p->right, r1, r2);
      
        /* In this node we are in state two, print node if this node
         * is not any of the two nodes r1 and r2. This makes sure that
         * is one random node is an ancestor of another random node
         * then it will not be printed instead its parent will be printed
         */
        if ((x == 0x03) && (p->data != r1) && (p->data != r2))
        {
         printf ("[%c] ", p->data);
         /* set state variable to 0 if we do not want to print 
          * the ancestors of the first ancestor 
          */
         x = 0;
        }
      
        /* return state variable to parent
         */    
        return x;
      }
      

      我认为这会正常工作,尽管我仍然要证明算法的正确性。 有一个缺点,如果一个节点是另一个节点的子节点,那么它只会打印另一个节点的父节点,而不是打印它们的父节点。 如果其中一个随机节点是另一个随机节点的祖先,然后它不会打印祖先随机节点,而是打印它的父节点。在其中一个随机节点是根节点的情况下,它不会打印任何内容,因为它始终是另一个随机节点的祖先,因此它们的共同祖先不存在。在这种特殊情况下,该函数将在main 中返回0x03,并且可以检测到。

      由于该算法进行后序遍历,因此它需要 O(n) 的执行时间和 O(n) 的内存。此外,一旦找到两个节点,搜索就会停止,节点越浅,搜索结束的速度就越快。

      更新

      这里有一些模式讨论:How to find the lowest common ancestor of two nodes in any binary tree?

      【讨论】:

      • 能否请您详细说明一下!
      • @phoxis:我认为你误解了这个问题。您的解决方案似乎回答了一个略有不同的问题。
      • @Adrian:我认为你是对的。它需要搜索祖先而不仅仅是父母。在那种情况下,我认为需要两个堆栈。让我努力,看看我是否可以改进答案。
      【解决方案5】:

      这个问题已经得到了很好的研究,并且有已知的算法可以在线性时间内解决它。 This paper 描述了您可以用来解决它的许多不同方法。诚然,这是一篇研究论文,所以算法有点棘手,但它描述的一些方法实际上是相当可行的。

      【讨论】:

        【解决方案6】:

        @Above,这不起作用,因为您假设两个节点都是某个特定节点的直接子节点...

                    8
             10           12
         7             
        

        我给出的节点是 7 和 12,答案必须是 8。 让我们这样做

            find(root, d1, d2, n1=null, n2=null)
             {
                  if(n1 && n2) return;
                  if(!root) return;
        
                  else  if(root -> d == d1 ) n1 = root;
                  else  if(root -> d == d2 ) n2 = root;                     
                  find(root->left, d1, d2, n1, n2);
                  find(root->right, d1, d2, n1, n2);
             }
        
             LCA(root, d1, d2)
             {
                 node *n1=null, *n2=null;
                 find(root, d1, d2, n1, n2);
                 if(n1 == null || n2 == null )error 'nodes not present' exit(0);
                 findIntersect(n1, n2); 
             }
             findInterSect(node *n1, node *n2)
             {
                l1 = length(n1);
                l2 = length(n2);
                node *g = n2, *l = n1;
                diff = abs(l1 - l2);
                if(l1>l2) g = n1 l =n2 
                while(diff) g = g->parent; diff--;
                // now both nodes are at same level
                while(g != l) g= g->parent, l = l->parent;
             }
        

        【讨论】:

          【解决方案7】:

          伪代码:

          node *FindCommonAncestor(node *root, node *node1, node *node2) {
            node *current = node1;
            node_list temp_list;
            temp_list.add(current);
            while (current != root) {
              current = current.parent;
              temp_list.add(current);
            }
            current = node2;
            while (current not in temp_list) {
              current = current.parent;
            }
            return current;
          }
          

          如果节点肯定是同一棵树的一部分,那么它们肯定会有一个共同的祖先(即使在最坏的情况下它是根)。所以它总是会终止,并且不需要担心错误情况。

          第一个循环运行 n 次,其中 n 是 node1 的深度,所以它是 O(n)。第二个循环运行 m 次,其中 m 在 node2 的深度。对临时列表的查找是(最坏的)n。所以第二个循环是O(m*n),它占主导地位,所以函数运行在O(m*n)。

          如果您对临时空间使用良好的集合数据结构(例如,哈希表)而不是列表,则可以将查找(通常)减少到 O(1),而不会增加向临时空间添加节点的成本.这将我们的函数时间减少到 O(m)。

          无论哪种方式,空间要求都是 O(n)。

          由于我们不提前知道n和m,所以我们用树中的节点总数来表示:S。如果树是平衡的,那么n和m分别以log_2(S ),因此运行时间为 O(log_2(S)^2)。 Log_2 非常强大,所以在我担心 2 的幂之前,S 必须变得非常大。如果树不平衡,那么我们会丢失 log_2(树实际上可能退化为链表)。所以绝对最坏的情况(当一个节点是根,另一个是完全退化树的叶子时)是 O(S^2)。

          【讨论】:

            【解决方案8】:

            您好,这将返回树根和 val1,val2 -> 正在传递节点的数据值的最低祖先节点值

            int CommonAncestor(node *root, int val1,int val2) 
            {
            
                if(root == NULL || (! root->left && ! root->right  )
                    return false;
            
                    while(root)
                    {
                        if(root->data < val1 && root->data < val2)
                         {
                            root = root->left;
                         }
                         else if(root->data > val1 && root->data > val2)
                        {
                            root= root->right;
                        }
                        else
                          return root->data;      
                    }
            }
            

            【讨论】:

              【解决方案9】:
              1. 前序遍历,除非满足任何1个节点,并保存当前访问的节点。

              2. 按顺序遍历,当满足(两个提供的节点中的)任何1个节点时开始保存节点,并保存列表直到遇到下一个节点。

              3. 后序遍历,当两个节点都被访问过时开始保存节点...
               一个
               B C 
               D E F G 
              H I J K L M N O 

              假设 H 和 E 是两个随机节点。

              1. ABDH
              2. HDIBJE
              3. EBLMENOGCA

              找到所有三个共有的第一个节点...

              【讨论】:

                【解决方案10】:

                以下是c#(.net)中的两种方法(都在上面讨论过)供参考:

                1. 在二叉树中查找 LCA 的递归版本(O(N) - 最多访问每个节点) (解决方案的要点是 LCA 是 (a) 在二叉树中只有两个元素位于子树(左右)两侧的节点是 LCA。(b) 而且哪一个节点出现在任何一边都没关系 - 最初我试图保留该信息,显然递归函数变得如此混乱。一旦我意识到它,它就变得非常优雅。

                2. 搜索两个节点 (O(N)),并跟踪路径(使用额外的空间 - 所以,#1 可能更优越,即使如果二叉树平衡良好,空间可能可以忽略不计,因为额外的内存消耗会在 O(log(N)) 中。

                  以便比较路径(本质上类似于接受的答案 - 但路径是通过假设二叉树节点中不存在指针节点来计算的)

                3. 仅用于补全(与问题无关),BST 中的 LCA (O(log(N))

                4. 测试

                递归:

                private BinaryTreeNode LeastCommonAncestorUsingRecursion(BinaryTreeNode treeNode, 
                            int e1, int e2)
                        {
                            Debug.Assert(e1 != e2);
                            
                            if(treeNode == null)
                            {
                                return null;
                            }
                            if((treeNode.Element == e1)
                                || (treeNode.Element == e2))
                            {
                                //we don't care which element is present (e1 or e2), we just need to check 
                                //if one of them is there
                                return treeNode;
                            }
                            var nLeft = this.LeastCommonAncestorUsingRecursion(treeNode.Left, e1, e2);
                            var nRight = this.LeastCommonAncestorUsingRecursion(treeNode.Right, e1, e2);
                            if(nLeft != null && nRight != null)
                            {
                                //note that this condition will be true only at least common ancestor
                                return treeNode;
                            }
                            else if(nLeft != null)
                            {
                                return nLeft;
                            }
                            else if(nRight != null)
                            {
                                return nRight;
                            }
                            return null;
                        }
                

                上面的私有递归版本是通过以下公共方法调用的:

                public BinaryTreeNode LeastCommonAncestorUsingRecursion(int e1, int e2)
                        {
                            var n = this.FindNode(this._root, e1);
                            if(null == n)
                            {
                                throw new Exception("Element not found: " + e1);
                            }
                            if (e1 == e2)
                            {   
                                return n;
                            }
                            n = this.FindNode(this._root, e2);
                            if (null == n)
                            {
                                throw new Exception("Element not found: " + e2);
                            }
                            var node = this.LeastCommonAncestorUsingRecursion(this._root, e1, e2);
                            if (null == node)
                            {
                                throw new Exception(string.Format("Least common ancenstor not found for the given elements: {0},{1}", e1, e2));
                            }
                            return node;
                        }
                

                通过跟踪两个节点的路径来解决:

                public BinaryTreeNode LeastCommonAncestorUsingPaths(int e1, int e2)
                        {
                            var path1 = new List<BinaryTreeNode>();
                            var node1 = this.FindNodeAndPath(this._root, e1, path1);
                            if(node1 == null)
                            {
                                throw new Exception(string.Format("Element {0} is not found", e1));
                            }
                            if(e1 == e2)
                            {
                                return node1;
                            }
                            List<BinaryTreeNode> path2 = new List<BinaryTreeNode>();
                            var node2 = this.FindNodeAndPath(this._root, e2, path2);
                            if (node1 == null)
                            {
                                throw new Exception(string.Format("Element {0} is not found", e2));
                            }
                            BinaryTreeNode lca = null;
                            Debug.Assert(path1[0] == this._root);
                            Debug.Assert(path2[0] == this._root);
                            int i = 0;
                            while((i < path1.Count)
                                && (i < path2.Count)
                                && (path2[i] == path1[i]))
                            {
                                lca = path1[i];
                                i++;
                            }
                            Debug.Assert(null != lca);
                            return lca;
                        }
                

                FindNodeAndPath 定义为

                private BinaryTreeNode FindNodeAndPath(BinaryTreeNode node, int e, List<BinaryTreeNode> path)
                        {
                            if(node == null)
                            {
                                return null;
                            }
                            if(node.Element == e)
                            {
                                path.Add(node);
                                return node;
                            }
                            var n = this.FindNodeAndPath(node.Left, e, path);
                            if(n == null)
                            {
                                n = this.FindNodeAndPath(node.Right, e, path);
                            }
                            if(n != null)
                            {
                                path.Insert(0, node);
                                return n;
                            }
                            return null;
                        }
                

                BST (LCA) - 不相关(仅供参考)

                public BinaryTreeNode BstLeastCommonAncestor(int e1, int e2)
                        {
                            //ensure both elements are there in the bst
                            var n1 = this.BstFind(e1, throwIfNotFound: true);
                            if(e1 == e2)
                            {
                                return n1;
                            }
                            this.BstFind(e2, throwIfNotFound: true);
                            BinaryTreeNode leastCommonAcncestor = this._root;
                            var iterativeNode = this._root;
                            while(iterativeNode != null)
                            {
                                if((iterativeNode.Element > e1 ) && (iterativeNode.Element > e2))
                                {
                                    iterativeNode = iterativeNode.Left;
                                }
                                else if((iterativeNode.Element < e1) && (iterativeNode.Element < e2))
                                {
                                    iterativeNode = iterativeNode.Right;
                                }
                                else
                                {
                                    //i.e; either iterative node is equal to e1 or e2 or in between e1 and e2
                                    return iterativeNode;
                                }
                            }
                            //control will never come here
                            return leastCommonAcncestor;
                        }
                

                单元测试

                [TestMethod]
                        public void LeastCommonAncestorTests()
                        {
                            int[] a = { 13, 2, 18, 1, 5, 17, 20, 3, 6, 16, 21, 4, 14, 15, 25, 22, 24 };
                            int[] b = { 13, 13, 13, 2, 13, 18, 13, 5, 13, 18, 13, 13, 14, 18, 25, 22};
                            BinarySearchTree bst = new BinarySearchTree();
                            foreach (int e in a)
                            {
                                bst.Add(e);
                                bst.Delete(e);
                                bst.Add(e);
                            }
                            for(int i = 0; i < b.Length; i++)
                            {
                                var n = bst.BstLeastCommonAncestor(a[i], a[i + 1]);
                                Assert.IsTrue(n.Element == b[i]);
                                var n1 = bst.LeastCommonAncestorUsingPaths(a[i], a[i + 1]);
                                Assert.IsTrue(n1.Element == b[i]);
                                Assert.IsTrue(n == n1);
                                var n2 = bst.LeastCommonAncestorUsingRecursion(a[i], a[i + 1]);
                                Assert.IsTrue(n2.Element == b[i]);
                                Assert.IsTrue(n2 == n1);
                                Assert.IsTrue(n2 == n);
                            }
                        }
                

                【讨论】:

                  猜你喜欢
                  • 2012-08-16
                  • 2012-01-16
                  • 2011-07-28
                  • 2012-11-08
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多