如何在任何二叉树中找到两个节点的最低共同祖先?

Posted

技术标签:

【中文标题】如何在任何二叉树中找到两个节点的最低共同祖先?【英文标题】:How to find the lowest common ancestor of two nodes in any binary tree? 【发布时间】:2010-12-01 20:27:00 【问题描述】:

这里的二叉树不一定是二叉搜索树。 该结构可以被视为 -

struct node 
    int data;
    struct node *left;
    struct node *right;
;

我可以和朋友一起解决的最大问题就是这样 - 考虑this binary tree:

中序遍历产生 - 8, 4, 9, 2, 5, 1, 6, 3, 7

后序遍历产生 - 8, 9, 4, 5, 2, 6, 7, 3, 1

例如,如果我们想找到节点 8 和 5 的共同祖先,那么我们在中序树遍历中列出 8 和 5 之间的所有节点,在这种情况下恰好是 [ 4、9、2]。然后我们检查该列表中的哪个节点在后序遍历中最后出现,即 2。因此 8 和 5 的共同祖先是 2。

这个算法的复杂度,我相信是 O(n)(对于中序/后序遍历是 O(n),其余步骤也是 O(n),因为它们只不过是数组中的简单迭代)。但很有可能这是错误的。 :-)

但这是一种非常粗糙的方法,我不确定它是否会在某些情况下失效。这个问题还有其他(可能更优化的)解决方案吗?

【问题讨论】:

出于好奇,这个有什么实际用途? @David:LCA 查询回答非常有用。 LCA + 后缀树 = 强大的字符串相关算法。 当我问了一个类似的问题时,它被 cmets 否决了,就像它的面试问题一样。 SO的对偶性? :( @Siddant +1 了解问题中给出的详细信息。 :) @DavidBrunelle 计算 LCA 的一个实际应用:它是渲染网页时必不可少的计算,特别是在计算适用于特定 DOM 元素的级联样式表 (CSS) 时。 【参考方案1】:

root 节点开始向下移动,如果您发现任何具有pq 作为其直接子节点的节点,那么它就是LCA。 (编辑 - 这应该是如果 pq 是节点的值,则返回它。否则当 pq 之一是另一个的直接子节点时它将失败。)

否则,如果您在其右(或左)子树中找到一个具有p 且在其左(或右)子树中具有q 的节点,那么它就是 LCA。

固定代码如下:

treeNodePtr findLCA(treeNodePtr root, treeNodePtr p, treeNodePtr q) 

        // no root no LCA.
        if(!root) 
                return NULL;
        

        // if either p or q is the root then root is LCA.
        if(root==p || root==q) 
                return root;
         else 
                // get LCA of p and q in left subtree.
                treeNodePtr l=findLCA(root->left , p , q);

                // get LCA of p and q in right subtree.
                treeNodePtr r=findLCA(root->right , p, q);

                // if one of p or q is in leftsubtree and other is in right
                // then root it the LCA.
                if(l && r) 
                        return root;
                
                // else if l is not null, l is LCA.
                else if(l) 
                        return l;
                 else 
                        return r;
                
        

当其中一个是另一个的直接子代时,以下代码将失败。

treeNodePtr findLCA(treeNodePtr root, treeNodePtr p, treeNodePtr q) 

        // no root no LCA.
        if(!root) 
                return NULL;
        

        // if either p or q is direct child of root then root is LCA.
        if(root->left==p || root->left==q || 
           root->right ==p || root->right ==q) 
                return root;
         else 
                // get LCA of p and q in left subtree.
                treeNodePtr l=findLCA(root->left , p , q);

                // get LCA of p and q in right subtree.
                treeNodePtr r=findLCA(root->right , p, q);

                // if one of p or q is in leftsubtree and other is in right
                // then root it the LCA.
                if(l && r) 
                        return root;
                
                // else if l is not null, l is LCA.
                else if(l) 
                        return l;
                 else 
                        return r;
                
        

Code In Action

【讨论】:

优雅的解决方案,但root==p || root==q => return root 位似乎过于乐观。如果发现根是 p/q,但另一个寻找的节点实际上不在树中怎么办? 我猜当 p 或 q 是不在二叉树中的值时,此代码会失败。我对吗?例如 LCA(8,20)。您的代码返回 8。但二叉树中不存在 20 这个解决方案的成本是多少?它有效率吗?即使在找到 p 和 q 之后,它似乎仍在继续搜索。这是因为 p 和 q 在树中可能不是唯一的,因为它不是 BST 并且可能包含重复项吗? @MikeB,这个解决方案肯定是 O(n),因为在最坏的情况下你只遍历每个节点一次。 Peter Lee,这是您在不使用父指针的情况下可以做到的最有效的方法。你有更好的解决方案吗? 第一个不完美的解决方案应该被删除,以免分散注意力【参考方案2】:

Nick Johnson 是正确的,如果您没有父指针,则 O(n) 时间复杂度算法是您能做的最好的算法。)有关该算法的简单递归版本,请参见 Kinding's post 中的代码,它在 O 中运行(n) 时间。

但请记住,如果您的节点有父指针,则可以改进算法。对于有问题的两个节点,通过从节点开始并在前面插入父节点,构造一个包含从根到节点的路径的列表。

因此,对于示例中的 8,您会得到(显示步骤):4、2、4、1、2、4

对有问题的其他节点执行相同操作,结果(未显示步骤):1, 2

现在比较您创建的两个列表,查找列表不同的第一个元素,或其中一个列表的最后一个元素,以先到者为准。

这个算法需要 O(h) 时间,其中 h 是树的高度。在最坏的情况下,O(h) 等价于 O(n),但如果树是平衡的,那就只有 O(log(n))。它还需要 O(h) 空间。一个改进的版本是可能的,它只使用常量空间,代码显示在CEGRD's post


无论树是如何构造的,如果这将是您在树上执行多次而不更改它的操作,那么您可以使用其他需要 O(n) [线性] 时间准备的算法,但是然后找到任何对只需要 O(1) [常数] 时间。有关这些算法的参考,请参阅Wikipedia 上的最低共同祖先问题页面。 (感谢 Jason 最初发布此链接)

【讨论】:

如果给出了父指针,它就可以完成这项工作。树中的节点与我在问题中给出的结构一样——只有左/右子指针,没有父指针。如果没有可用的父指针,并且树不是二叉搜索树,而只是二叉树,是否有任何 O(log(n)) 解决方案? 如果你没有找到父节点和给定节点之间路径的特殊方法,那么平均需要 O(n) 时间才能找到它。这将使得不可能有 O(log(n)) 时间。但是,如果您要多次执行此操作而不更改其间的树,那么 O(n) 一次成本和 O(1) 对查找可能是您最好的选择。否则,如果可能的话,您应该添加父指针。它可以使相当多的潜在算法更快,但我很确定它不会改变任何现有算法的顺序。希望这会有所帮助。 这种方法可以使用 O(1) 内存来完成 -- 请参阅 ***.com/questions/1594061/… 上的 Artelius(和其他人)解决方案 @Tom:确实,对于基于列表的算法,这可以将内存复杂度限制为 O(1)。显然,这意味着每边对树本身进行一次迭代以获取节点的深度,然后(部分)第二次迭代以找到共同的祖先。 O(h) 时间和 O(1) 空间对于有父指针且不进行 O(n) 预计算的情况显然是最佳的。 @ALBI O(h) 只有在树平衡的情况下才为 O(log(n))。对于任何树,无论是否是二元树,如果您有父指针,您可以在O(h) 时间确定从叶子到根的路径,只需跟随父指针直到h 时间。这为您提供了从叶子到根的路径。如果路径存储为堆栈,则迭代堆栈将为您提供从根到叶的路径。如果您缺少父指针,并且树没有特殊结构,那么找到从根到叶的路径确实需要O(n) 时间。【参考方案3】:

这是 JAVA 中的工作代码

public static Node LCA(Node root, Node a, Node b) 
   if (root == null) 
       return null;
   

   // If the root is one of a or b, then it is the LCA
   if (root == a || root == b) 
       return root;
   

   Node left = LCA(root.left, a, b);
   Node right = LCA(root.right, a, b);

   // If both nodes lie in left or right then their LCA is in left or right,
   // Otherwise root is their LCA
   if (left != null && right != null) 
      return root;
   

   return (left != null) ? left : right; 

【讨论】:

当树中不存在节点时,这不起作用。 如果给定的树是 BST,你会优化你的代码吗? "如果根是 a 或 b 之一,那么它就是 LCA。"这可能不是真的。你现在知道的是,你不需要检查它的任何孩子来找到 LCA。发生这种情况是因为我们稍后可以检查根的父级是否在两个分支(LCA 是父级)或其中一个(在这种情况下可能是 LCA,或者更大的祖先可能是 LCA )。【参考方案4】:

到目前为止给出的答案使用递归或存储,例如,内存中的路径。

如果您的树很深,这两种方法都可能会失败。

这是我对这个问题的看法。 当我们检查两个节点的深度(到根的距离)时,如果它们相等,那么我们可以安全地从两个节点向上移动到共同的祖先。如果其中一个深度较大,那么我们应该从较深的节点向上移动,同时留在另一个节点。

代码如下:

findLowestCommonAncestor(v,w):
  depth_vv = depth(v);
  depth_ww = depth(w);

  vv = v; 
  ww = w;

  while( depth_vv != depth_ww ) 
    if ( depth_vv > depth_ww ) 
      vv = parent(vv);
      depth_vv--;
    else 
      ww = parent(ww);
      depth_ww--;
    
  

  while( vv != ww ) 
    vv = parent(vv);
    ww = parent(ww);
  

  return vv;    

该算法的时间复杂度为:O(n)。 该算法的空间复杂度为:O(1)。

关于深度的计算,我们先记住定义:如果v是根,depth(v) = 0;否则,depth(v) = depth(parent(v)) + 1。我们可以如下计算深度:

depth(v):
  int d = 0;
  vv = v;
  while ( vv is not root ) 
    vv = parent(vv);
    d++;
  
  return d;

【讨论】:

二叉树通常没有对父元素的引用。添加父引用可以毫无问题地完成,但我会考虑 O(n) 辅助空间。 这个解决方案有一个微妙的假设。如果一个节点是另一个节点的直接或间接父节点(即,较深节点位于以较浅节点为根的树中),则此解决方案将返回较浅节点的父节点作为结果。根据您定义最低共同祖先的方式,这可能不是您想要的。一些定义将要求较浅的节点本身作为父节点。在这种情况下,您需要跟踪哪个是较浅的节点并将其返回。【参考方案5】:

嗯,这取决于你的二叉树的结构。假设您有某种方法可以在给定树根的情况下找到所需的叶节点 - 只需将其应用于两个值,直到您选择的分支发散。

如果您没有办法在给定根的情况下找到所需的叶子,那么您唯一的解决方案 - 无论是在正常操作中还是找到最后一个公共节点 - 都是对树进行暴力搜索。

【讨论】:

【参考方案6】:

这可以在以下位置找到:- http://goursaha.freeoda.com/DataStructure/LowestCommonAncestor.html

 tree_node_type *LowestCommonAncestor(
 tree_node_type *root , tree_node_type *p , tree_node_type *q)
 
     tree_node_type *l , *r , *temp;
     if(root==NULL)
     
        return NULL;
     

    if(root->left==p || root->left==q || root->right ==p || root->right ==q)
    
        return root;
    
    else
    
        l=LowestCommonAncestor(root->left , p , q);
        r=LowestCommonAncestor(root->right , p, q);

        if(l!=NULL && r!=NULL)
        
            return root;
        
        else
        
        temp = (l!=NULL)?l:r;
        return temp;
        
    

【讨论】:

您能否告诉我,如果 p 存在但 q 根本不存在于树中,您的代码将如何表现?同样,p 和 q 都不存在。谢谢!!! 时间方面的大 O 是多少?我认为是 O(n*log(n)),两个慢。【参考方案7】:

Tarjan's off-line least common ancestors algorithm 已经足够好了(参见Wikipedia)。 Wikipedia 上有更多关于这个问题(最低共同祖先问题)的内容。

【讨论】:

【参考方案8】:

找出两个节点的共同祖先:-

使用二分搜索在树中找到给定的节点 Node1,并将此过程中访问的所有节点保存在一个数组中,比如 A1。时间 - O(logn),空间 - O(logn) 使用二分搜索在树中找到给定的 Node2,并将此过程中访问的所有节点保存在数组 A2 中。时间 - O(logn),空间 - O(logn) 如果 A1 列表或 A2 列表为空,则该节点不存在,因此没有共同祖先。 如果 A1 列表和 A2 列表不为空,则查看列表,直到找到不匹配的节点。一旦找到这样的节点,那么在该节点之前的节点就是共同祖先。

这适用于二叉搜索树。

【讨论】:

他明确表示这棵树不一定是 BST。 @Peter Lee - 即使对任何二叉树进行简单更改,上述逻辑也适用。代替给定节点的二进制搜索,应用线性搜索(即任何遍历,但对于两种情况应该相同)。当然运行时间将是 O(n) 而不是 O(logn)。事实上,当父指针不可用时,此算法是最强大的算法。当给定节点之一不属于树时,许多(即'codaddict')给出的粗略算法将不起作用)【参考方案9】:

我已经尝试使用 Java 中的说明性图片和工作代码,

http://tech.bragboy.com/2010/02/least-common-ancestor-without-using.html

【讨论】:

【参考方案10】:

对于平衡二叉树,以下递归算法将以 O(log N) 运行。如果传递给 getLCA() 函数的任何一个节点与根相同,则根将是 LCA,无需执行任何返回。

测试用例。 [1] 两个节点 n1 和 n2 都在树中,并且位于其父节点的任一侧。 [2] 节点 n1 或 n2 是根,LCA 是根。 [3] 树中只有 n1 或 n2,LCA 将是树根的左子树的根节点,或者 LCA 将是树的右子树的根节点根。

[4] n1 或 n2 都不在树中,没有 LCA。 [5] n1 和 n2 都在一条直线上相邻,LCA 将是靠近树根的 n1 或 n2。

//find the search node below root
bool findNode(node* root, node* search)

    //base case
    if(root == NULL)
        return false;

    if(root->val == search->val)
        return true;

    //search for the node in the left and right subtrees, if found in either return true
    return (findNode(root->left, search) || findNode(root->right, search));


//returns the LCA, n1 & n2 are the 2 nodes for which we are
//establishing the LCA for
node* getLCA(node* root, node* n1, node* n2)

    //base case
    if(root == NULL)
        return NULL;

    //If 1 of the nodes is the root then the root is the LCA
    //no need to recurse.
    if(n1 == root || n2 == root)
        return root;

    //check on which side of the root n1 and n2 reside
    bool n1OnLeft = findNode(root->left, n1);
    bool n2OnLeft = findNode(root->left, n2);

    //n1 & n2 are on different sides of the root, so root is the LCA
    if(n1OnLeft != n2OnLeft)
        return root;

    //if both n1 & n2 are on the left of the root traverse left sub tree only
    //to find the node where n1 & n2 diverge otherwise traverse right subtree
    if(n1OnLeft)
        return getLCA(root->left, n1, n2);
    else
        return getLCA(root->right, n1, n2);

【讨论】:

【参考方案11】:

只要两个给定节点,比如必须找到 Ancestor 的 pq,都在同一个子树中(意味着它们的值都是小于或都大于根的)。

这会直接从根走到 Least Common Ancestor ,而不是看树的其余部分,所以它几乎和它一样快。几种方法。

迭代,O(1) 空间

Python

def lowestCommonAncestor(self, root, p, q):
    while (root.val - p.val) * (root.val - q.val) > 0:
        root = (root.left, root.right)[p.val > root.val]
    return root

Java

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) 
    while ((root.val - p.val) * (root.val - q.val) > 0)
        root = p.val < root.val ? root.left : root.right;
    return root;

如果溢出,我会这样做 (root.val - (long)p.val) * (root.val - (long)q.val)

递归

Python

def lowestCommonAncestor(self, root, p, q):
    next = p.val < root.val > q.val and root.left or \
           p.val > root.val < q.val and root.right
    return self.lowestCommonAncestor(next, p, q) if next else root

Java

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) 
    return (root.val - p.val) * (root.val - q.val) < 1 ? root :
           lowestCommonAncestor(p.val < root.val ? root.left : root.right, p, q);

【讨论】:

【参考方案12】:
Node *LCA(Node *root, Node *p, Node *q) 
  if (!root) return NULL;
  if (root == p || root == q) return root;
  Node *L = LCA(root->left, p, q);
  Node *R = LCA(root->right, p, q);
  if (L && R) return root;  // if p and q are on both sides
  return L ? L : R;  // either one of p,q is on one side OR p,q is not in L&R subtrees

【讨论】:

【参考方案13】:

考虑一下这棵树

如果我们进行后序和前序遍历并找到第一个出现的共同前任和继任者,我们就会得到共同祖先。

postorder => 0,2,1,5,4,6,3,8,10,11,9,14,15,13,​​12,7 预购 => 7,3,1,0,2,6,4,5,12,9,8,11,10,13,15,14

例如:1

8,11 的最小共同祖先

在后序中,我们在 8 和 11 之后有 = >9,14,15,13,​​12,7 在预购中,我们在 8 和 11 之前有 =>7,3,1,0,2,6,4,5,12,9

9 是出现在后序中 8 和 11 之后和前序中 8 和 11 之前的第一个公共数字,因此 9 是答案

例如:2

5,10 的最小共同祖先

11,9,14,15,13,​​12,7 后序 7,3,1,0,2,6,4 预购

7 是后序中 5,10 之后和前序中 5,10 之前出现的第一个数字,因此 7 是答案

【讨论】:

【参考方案14】:

如果它是完整的二叉树,节点 x 的子节点为 2*x 和 2*x+1,那么有更快的方法来做到这一点

int get_bits(unsigned int x) 
  int high = 31;
  int low = 0,mid;
  while(high>=low) 
    mid = (high+low)/2;
    if(1<<mid==x)
      return mid+1;
    if(1<<mid<x) 
      low = mid+1;
    
    else 
      high = mid-1;
    
  
  if(1<<mid>x)
    return mid;
  return mid+1;


unsigned int Common_Ancestor(unsigned int x,unsigned int y) 

  int xbits = get_bits(x);
  int ybits = get_bits(y);
  int diff,kbits;
  unsigned int k;
  if(xbits>ybits) 
    diff = xbits-ybits;
    x = x >> diff;
  
  else if(xbits<ybits) 
    diff = ybits-xbits;
    y = y >> diff;
  
  k = x^y;
  kbits = get_bits(k);
  return y>>kbits;  

它是如何工作的

    获取表示 x 和 y 所需的位,使用二进制搜索是 O(log(32)) x & y 二进制符号的共同前缀是共同祖先 以较大的位数表示的那个被 k 带到同一位 >> diff k = x^y 擦除 x & y 的公共前缀 查找表示剩余后缀的位 将 x 或 y 移动后缀位以获得共同前缀,即共同祖先。

这是有效的,因为基本上递归地将较大的数字除以二,直到两个数字相等。这个数字就是共同的祖先。除法实际上是右移操作。所以我们需要找到两个数字的公共前缀才能找到最近的祖先

【讨论】:

【参考方案15】:

在 Scala 中,您可以:

  abstract class Tree
  case class Node(a:Int, left:Tree, right:Tree) extends Tree
  case class Leaf(a:Int) extends Tree

  def lca(tree:Tree, a:Int, b:Int):Tree = 
    tree match 
      case Node(ab,l,r) => 
        if(ab==a || ab ==b) tree else 
          val temp = lca(l,a,b);
          val temp2 = lca(r,a,b);
          if(temp!=null && temp2 !=null)
            tree
          else if (temp==null && temp2==null)
            null
          else if (temp==null) r else l
        

      
      case Leaf(ab) => if(ab==a || ab ==b) tree else null
    
  

【讨论】:

【参考方案16】:

node1node2 两个节点之间的最低共同祖先是树中同时具有两个节点作为后代的最低节点。

从根节点开始遍历二叉树,直到找到两个节点。每次访问节点时,都会将其添加到字典中(称为parent)。 在二叉树中找到两个节点后,使用字典获取 node1 的祖先并将其添加到集合中(称为 ancestors)。 以与node2 相同的方式执行此步骤。如果node2 的祖先存在于node1 的祖先集中,则它是它们之间的第一个共同祖先。

下面是使用stackdictionary实现的迭代python方案,有以下几点:

一个节点可以是它自己的后代 二叉树中的所有节点都是唯一的 node1node2 存在于二叉树中
class Node:
    def __init__(self, data=None, left=None, right=None):
        self.data  = data
        self.left  = left
        self.right = right

def lowest_common_ancestor(root, node1, node2):
    parent = root: None
    stack = [root]
    
    while node1 not in parent or node2 not in parent:
        node = stack[-1]
        stack.pop()
        if node.left:
            parent[node.left] = node
            stack.append(node.left)
        if node.right:
            parent[node.right] = node
            stack.append(node.right)
    
    ancestors = set()
    while node1:
        ancestors.add(node1)
        node1 = parent[node1]
    while node2 not in ancestors:
        node2 = parent[node2]
    
    return node2.data

def main():
    '''
    Construct the below binary tree:

            30
           /  \
          /    \
         /      \
        11      29
       /  \    /  \
      8   12  25  14

    '''
    root = Node(30)
    root.left  = Node(11)
    root.right = Node(29)
    root.left.left  = Node(8)
    root.left.right = Node(12)
    root.right.left  = Node(25)
    root.right.right = Node(14)

    print(lowest_common_ancestor(root, root.left.left, root.left.right))       # 11
    print(lowest_common_ancestor(root, root.left.left, root.left))             # 11
    print(lowest_common_ancestor(root, root.left.left, root.right.right))      # 30


if __name__ == '__main__':
    main()

这种方法的复杂度是:O(n)

【讨论】:

【参考方案17】:
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) 
        if(root==null || root == p || root == q)
            return root;
        
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        return left == null ? right : right == null ? left : root;
    

【讨论】:

【参考方案18】:

这是 C++ 的实现方式。已尝试使算法尽可能易于理解:

// Assuming that `BinaryNode_t` has `getData()`, `getLeft()` and `getRight()`
class LowestCommonAncestor

  typedef char type;    
  // Data members which would behave as place holders
  const BinaryNode_t* m_pLCA;
  type m_Node1, m_Node2;

  static const unsigned int TOTAL_NODES = 2;

  // The core function which actually finds the LCA; It returns the number of nodes found
  // At any point of time if the number of nodes found are 2, then it updates the `m_pLCA` and once updated, we have found it!
  unsigned int Search (const BinaryNode_t* const pNode)
  
    if(pNode == 0)
      return 0;

    unsigned int found = 0;

    found += (pNode->getData() == m_Node1);
    found += (pNode->getData() == m_Node2);

    found += Search(pNode->getLeft()); // below condition can be after this as well
    found += Search(pNode->getRight());

    if(found == TOTAL_NODES && m_pLCA == 0)
      m_pLCA = pNode;  // found !

    return found;
  

public:
  // Interface method which will be called externally by the client
  const BinaryNode_t* Search (const BinaryNode_t* const pHead,
                              const type node1,
                              const type node2)
  
    // Initialize the data members of the class
    m_Node1 = node1;
    m_Node2 = node2;
    m_pLCA = 0;

    // Find the LCA, populate to `m_pLCANode` and return
    (void) Search(pHead);
    return m_pLCA;
  
;

使用方法:

LowestCommonAncestor lca;
BinaryNode_t* pNode = lca.Search(pWhateverBinaryTreeNodeToBeginWith);
if(pNode != 0)
  ...

【讨论】:

【参考方案19】:

找到最低共同祖先的最简单方法是使用以下算法:

检查根节点 如果 value1 和 value2 严格小于根节点的值 检查左子树 否则,如果 value1 和 value2 严格大于根节点处的值 检查右子树 别的 返回根
public int LCA(TreeNode root, int value 1, int value 2) 
    while (root != null) 
       if (value1 < root.data && value2 < root.data)
           return LCA(root.left, value1, value2);
       else if (value2 > root.data && value2 2 root.data)
           return LCA(root.right, value1, value2);
       else
           return root
    

    return null;
 

【讨论】:

【参考方案20】:

我找到了解决办法

    请按顺序 接受预订 接受后购

根据 3 次遍历,您可以决定谁是 LCA。 从 LCA 找到两个节点的距离。 将这两个距离相加,就是答案。

【讨论】:

【参考方案21】:

这是我的想法,

    找到第一个节点的路由,存储到arr1。 开始寻找 2 节点的路由,同时检查从根到 arr1 的每个值。 值不同的时间,退出。旧匹配值是 LCA。

复杂性: 第 1 步:O(n),第 2 步 =~ O(n),总计 =~ O(n)。

【讨论】:

【参考方案22】:

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

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

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

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

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

    测试

递归:

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);
            
        

【讨论】:

【参考方案23】:

如果有人对伪代码(大学家庭作业)感兴趣,这里就是其中之一。

GETLCA(BINARYTREE BT, NODE A, NODE  B)
IF Root==NIL
    return NIL
ENDIF

IF Root==A OR root==B
    return Root
ENDIF

Left = GETLCA (Root.Left, A, B)
Right = GETLCA (Root.Right, A, B)

IF Left! = NIL AND Right! = NIL
    return root
ELSEIF Left! = NIL
    Return Left
ELSE
    Return Right
ENDIF

【讨论】:

【参考方案24】:

虽然已经回答了这个问题,但这是我使用 C 编程语言解决这个问题的方法。尽管代码显示了二叉搜索树(就 insert() 而言),但该算法也适用于二叉树。这个想法是在中序遍历中遍历从节点 A 到节点 B 的所有节点,在后序遍历中查找这些节点的索引。后序遍历中具有最大索引的节点是最低的共同祖先。

这是一个有效的 C 代码,用于实现在二叉树中查找最低共同祖先的函数。我还提供了所有实用功能等,但请跳转到 CommonAncestor() 以快速了解。

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <math.h>

static inline int min (int a, int b)

    return ((a < b) ? a : b);

static inline int max (int a, int b)

    return ((a > b) ? a : b);


typedef struct node_ 
    int value;
    struct node_ * left;
    struct node_ * right;
 node;

#define MAX 12

int IN_ORDER[MAX] = 0;
int POST_ORDER[MAX] = 0;

createNode(int value) 

    node * temp_node = (node *)malloc(sizeof(node));
    temp_node->left = temp_node->right = NULL;
    temp_node->value = value;
    return temp_node;


node *
insert(node * root, int value)

    if (!root) 
        return createNode(value);
    

    if (root->value > value) 
        root->left = insert(root->left, value);
     else 
        root->right = insert(root->right, value);
    

    return root;



/* Builds inorder traversal path in the IN array */
void
inorder(node * root, int * IN)

    static int i = 0;

    if (!root) return;

    inorder(root->left, IN);
    IN[i] = root->value;
    i++;
    inorder(root->right, IN);


/* Builds post traversal path in the POST array */

void
postorder (node * root, int * POST)

    static int i = 0;

    if (!root) return;

    postorder(root->left, POST);
    postorder(root->right, POST);
    POST[i] = root->value;
    i++;



int
findIndex(int * A, int value)

    int i = 0;
    for(i = 0; i< MAX; i++) 
        if(A[i] == value) return i;
    

int
CommonAncestor(int val1, int val2)

    int in_val1, in_val2;
    int post_val1, post_val2;
    int j=0, i = 0; int max_index = -1;

    in_val1 = findIndex(IN_ORDER, val1);
    in_val2 = findIndex(IN_ORDER, val2);
    post_val1 = findIndex(POST_ORDER, val1);
    post_val2 = findIndex(POST_ORDER, val2);

    for (i = min(in_val1, in_val2); i<= max(in_val1, in_val2); i++) 
        for(j = 0; j < MAX; j++) 
            if (IN_ORDER[i] == POST_ORDER[j]) 
                if (j > max_index) 
                    max_index = j;
                
            
        
    
    printf("\ncommon ancestor of %d and %d is %d\n", val1, val2, POST_ORDER[max_index]);
    return max_index;

int main()

    node * root = NULL; 

    /* Build a tree with following values */
    //40, 20, 10, 30, 5, 15, 25, 35, 1, 80, 60, 100
    root = insert(root, 40);
    insert(root, 20);
    insert(root, 10);
    insert(root, 30);
    insert(root, 5);
    insert(root, 15);
    insert(root, 25);
    insert(root, 35);
    insert(root, 1);
    insert(root, 80);
    insert(root, 60);
    insert(root, 100);

    /* Get IN_ORDER traversal in the array */
    inorder(root, IN_ORDER);

    /* Get post order traversal in the array */
    postorder(root, POST_ORDER);

    CommonAncestor(1, 100);



【讨论】:

【参考方案25】:

还有一种方法。但是,它不像答案中已经建议的那样有效。

为节点 n1 创建一个路径向量。

为节点 n2 创建第二个路径向量。

暗示集合节点的路径向量将遍历该节点以到达相关节点。

比较两个路径向量。它们不匹配的索引,返回该索引处的节点 - 1。这将给出 LCA。

这种方法的缺点:

需要遍历树两次来计算路径向量。 需要额外的 O(h) 空间来存储路径向量。

但是,这也很容易实现和理解。

计算路径向量的代码:

private boolean findPathVector (TreeNode treeNode, int key, int pathVector[], int index) 

        if (treeNode == null) 
            return false;
        

        pathVector [index++] = treeNode.getKey ();

        if (treeNode.getKey () == key) 
            return true;
        
        if (findPathVector (treeNode.getLeftChild (), key, pathVector, index) || 
            findPathVector (treeNode.getRightChild(), key, pathVector, index)) 

            return true;        
        

        pathVector [--index] = 0;
        return false;       
    

【讨论】:

【参考方案26】:

这样试试

node * lca(node * root, int v1,int v2)


if(!root) 
            return NULL;
    
    if(root->data == v1 || root->data == v2) 
        return root;
    else
    
        if((v1 > root->data && v2 < root->data) || (v1 < root->data && v2 > root->data))
        
            return root;
        

        if(v1 < root->data && v2 < root->data)
        
            root = lca(root->left, v1, v2);
        

        if(v1 > root->data && v2 > root->data)
        
            root = lca(root->right, v1, v2);
        
    
return root;

【讨论】:

【参考方案27】:

粗略方式:

在每个节点 X = 查找 n1、n2 中的任何一个是否存在于节点的左侧 Y = 查找 n1、n2 中的任何一个是否存在于节点的右侧 如果节点本身是n1 || n2,我们可以称它为在左边找到的 或出于概括目的的权利。 如果 X 和 Y 都为真,那么节点就是 CA

上述方法的问题是我们将多次进行“查找”,即每个节点都有可能被多次遍历。 如果我们可以记录信息以便不再处理它(想想动态编程),我们就可以克服这个问题。

因此,我们不是查找每个节点,而是记录已经找到的内容。

更好的方法:

我们以深度优先的方式检查给定节点是否 left_set(意味着 n1 | n2 已在左子树中找到)或 right_set。 (注意:如果它是 n1 | n2,我们将赋予根本身作为 left_set 的属性) 如果 left_set 和 right_set 都存在,则该节点是 LCA。

代码:

struct Node *
findCA(struct Node *root, struct Node *n1, struct Node *n2, int *set) 
   int left_set, right_set;
   left_set = right_set = 0;
   struct Node *leftCA, *rightCA;
   leftCA = rightCA = NULL;

   if (root == NULL) 
      return NULL;
   
   if (root == n1 || root == n2) 
      left_set = 1;
      if (n1 == n2) 
         right_set = 1;
      
   

   if(!left_set) 
      leftCA = findCA(root->left, n1, n2, &left_set);
      if (leftCA) 
         return leftCA;
      
   
   if (!right_set) 
      rightCA= findCA(root->right, n1, n2, &right_set);
      if(rightCA) 
         return rightCA;
      
   

   if (left_set && right_set) 
      return root;
    else 
      *set = (left_set || right_set);
      return NULL;
   

【讨论】:

【参考方案28】:

广度优先搜索的代码,以确保两个节点都在树中。 只有这样才能继续进行 LCA 搜索。 如果您有任何改进的建议,请发表评论。 我认为我们可以将它们标记为已访问并在我们停止改进第二个节点的某个点重新开始搜索(如果未找到已访问)

public class searchTree 
    static boolean v1=false,v2=false;
    public static boolean bfs(Treenode root, int value)
         if(root==null)
           return false;
     
    Queue<Treenode> q1 = new LinkedList<Treenode>();

    q1.add(root);
    while(!q1.isEmpty())
    
        Treenode temp = q1.peek();

        if(temp!=null) 
            q1.remove();
            if (temp.value == value) return true;
            if (temp.left != null) q1.add(temp.left);
            if (temp.right != null) q1.add(temp.right);
        
    
    return false;


public static Treenode lcaHelper(Treenode head, int x,int y)

    if(head==null)
        return null;
    

    if(head.value == x || head.value ==y)
        if (head.value == y)
            v2 = true;
            return head;
        
        else 
            v1 = true;
            return head;
        
    

    Treenode left = lcaHelper(head.left, x, y);
    Treenode right = lcaHelper(head.right,x,y);

    if(left!=null && right!=null)
        return head;
    
    return (left!=null) ? left:right;


public static int lca(Treenode head, int h1, int h2) 
    v1 = bfs(head,h1);
    v2 = bfs(head,h2);
    if(v1 && v2)
        Treenode lca = lcaHelper(head,h1,h2);
        return lca.value;
    
    return -1;


【讨论】:

【参考方案29】:

这里的一些解决方案假设存在对根节点的引用,一些假设树是 BST。 使用 hashmap 分享我的解决方案,不参考 root 节点和树可以是 BST 或非 BST:

    var leftParent : Node? = left
    var rightParent : Node? = right
    var map = [data : Node?]()

    while leftParent != nil 
        map[(leftParent?.data)!] = leftParent
        leftParent = leftParent?.parent
    

    while rightParent != nil 
        if let common = map[(rightParent?.data)!] 
            return common
        
        rightParent = rightParent?.parent
    

【讨论】:

【参考方案30】:

解决方案 1:递归 - 更快

这个想法是从根开始遍历树。如果任何给定的键 p 和 q 与 root 匹配,则 root 是 LCA,假设两个键都存在。如果根不匹配任何键,我们递归左子树和右子树。 左子树中存在一个键且右子树中存在另一个键的节点是 LCA。如果两个key都在左子树,那么左子树也有LCA,否则LCA在右子树。
时间复杂度:O(n) 空间复杂度:O(h) - 用于递归调用堆栈
class Solution 

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
    
        if(root == null || root == p  || root == q)
            return root;

        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);

        if(left == null)
            return right;
        else if(right == null)
            return left;
        else
            return root;    // If(left != null && right != null)
    

解决方案 2:迭代 - 使用父指针 - 更慢

创建一个空的哈希表。 在哈希表中插入 p 及其所有祖先。 检查 q 或其任何祖先是否存在于哈希表中,如果存在则返回第一个现有祖先。
时间复杂度:O(n) - 在最坏的情况下,我们可能会访问二叉树的所有节点。 空间复杂度:O(n) - 使用父指针哈希表、祖先集和队列的空间将是 O(n)。
class Solution

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
    
        HashMap<TreeNode, TreeNode> parent_map = new HashMap<>();
        HashSet<TreeNode> ancestors_set = new HashSet<>();
        Queue<TreeNode> queue = new LinkedList<>();

        parent_map.put(root, null);
        queue.add(root);

        while(!parent_map.containsKey(p) || !parent_map.containsKey(q))
        
            TreeNode node = queue.poll();

            if(node.left != null)
            
                parent_map.put(node.left, node);
                queue.add(node.left);
            
            if(node.right != null)
            
                parent_map.put(node.right, node);
                queue.add(node.right);
            
        

        while(p != null)
        
            ancestors_set.add(p);
            p = parent_map.get(p);
        

        while(!ancestors_set.contains(q))
            q = parent_map.get(q);

        return q;
    

【讨论】:

以上是关于如何在任何二叉树中找到两个节点的最低共同祖先?的主要内容,如果未能解决你的问题,请参考以下文章

如果不是树中的所有这些节点,Python会在二叉树中找到两个节点的最低共同祖先

寻找二叉树中的最低公共祖先结点----LCA(Lowest Common Ancestor )问题(递归)

使用递归js查找树状对象(不是二叉树)的最低共同祖先

在给定二叉树中的两个值的情况下找到最不共同的祖先[关闭]

python代码实现二叉树中最低的公共祖先

236. 二叉树的最近公共祖先[中等]