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

时间:2009-09-27 21:01:42

标签: algorithm binary-tree complexity-theory least-common-ancestor

这里的二叉树可能不一定是二进制搜索树 结构可以视为 -

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

我可以与朋友一起解决的最大解决方案就是这样 -
考虑this binary tree

Binary Tree

inorder遍历产量 - 8,4,9,2,5,1,6,3,7

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

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

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

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

33 个答案:

答案 0 :(得分:105)

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

答案 1 :(得分:69)

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最初发布此链接)

答案 2 :(得分:49)

这是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; 
}

答案 3 :(得分:27)

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

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

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

以下是代码:

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是root,则depth(v)= 0;否则,深度(v)=深度(父(v))+ 1.我们可以如下计算深度:

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

答案 4 :(得分:7)

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

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

答案 5 :(得分:7)

可在以下网址找到: - 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;
        }
    }
}

答案 6 :(得分:5)

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

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

这适用于二叉搜索树。

答案 7 :(得分:5)

Tarjan's off-line least common ancestors algorithm足够好了(参见Wikipedia)。 Wikipedia上的问题(最低的共同祖先问题)还有很多。

答案 8 :(得分:3)

我尝试用Java编写说明图片和工作代码,

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

答案 9 :(得分:2)

两个节点node1node2之间的最低公共祖先是将两个节点都作为后代的树中的最低节点。

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

以下是使用 stack dictionary 实现的迭代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)

答案 10 :(得分:2)

只要走到整个树的root,只要两个给定的节点,比如说pq,必须找到祖先,就在相同的子树(意味着它们的值都比较小或者都大于根)。

它直接从根部走向最不常见的祖先,而不是看着树的其余部分,所以它的速度和它一样快。有几种方法可以做到。

  

迭代,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
  
    

<强>爪哇

  
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
  
    

<强>爪哇

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

答案 11 :(得分:2)

以下递归算法将在O(log N)中运行,以获得平衡二叉树。如果传入getLCA()函数的任何一个节点都与root相同,那么root将是LCA,并且不需要执行任何recussrion。

测试用例。 [1] 节点n1和&amp; 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);
}

答案 12 :(得分:1)

在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
}
}

答案 13 :(得分:1)

考虑这个树enter image description here

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

postorder =&gt; 0,2,1,5,4,6,3,8,10,11,9,14,15,13,​​12,7    preorder =&gt; 7,3,1,0,2,6,4,5,12,9,8,11,10,13,15,14

  • 例如:1

最少的共同祖先8,11

在后序中我们在8&amp;之后得到=&gt; 9,14,15,13,​​12,7。 11 在预购中,我们在8&amp;之前有=&gt; 7,3,1,0,2,6,4,5,12,9。 11

9是8&amp;之后出现的第一个常见数字。 11在后序和8&amp;之前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 :(得分:1)

如果它是完整的二叉树,节点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;  
}

如何运作

  
      
  1. 获取代表x&amp;使用二进制搜索的y是O(log(32))
  2.   
  3. x&amp;的二进制表示法的公共前缀y是共同的祖先
  4.   
  5. 由较大的比特数表示的任何一个由k>&gt;带到相同的比特。 DIFF
  6.   
  7. k = x ^ y擦除x&amp;的公共前缀ÿ
  8.   
  9. 找到代表剩余后缀的位
  10.   
  11. 通过后缀位移动x或y以获得共同前缀,这是共同的祖先。
  12.   

这是有效的,因为基本上递增地将较大的数字除以2,直到两个数字相等。这个数字是共同的祖先。划分实际上是正确的转变操作。所以我们需要找到两个数字的公共前缀来找到最近的祖先

答案 15 :(得分:0)

虽然已经回答了这个问题,但这是我使用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);


}

答案 16 :(得分:0)

以下是到达solve this的最快方法。空间复杂度O(1),时间复杂度O(n)。如果

  

如果一个节点的left和right值都不为null,则该节点为   您的答案(第三个“如果”从顶部开始)。在迭代是否找到值时,如   所有的值都是唯一的,必须存在,我们不需要   搜索其后代。因此,只需返回找到的匹配节点即可。如果   节点的左右分支内容null传播null   向上。到达顶级递归时,如果返回一个分支   价值,当其他两个都返回时,其他人就不会继续传播该价值   不为null返回当前节点。如果达到根级别递归   一个null,另一个不为null,返回不为null的值,作为问题,   承诺值存在,它必须在   找到了我们从未遍历的节点。

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null) return null;
        if(root.val == p.val || root.val == q.val) return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if (left != null && right !=null) return root;
        if (left == null && right ==null) return null;
        return (left != null ? left : right);
    }
}

enter image description here

答案 17 :(得分:0)

解决方案1:递归-更快

  • 这个想法是从根开始遍历树。如果给定密钥p和q中的任何一个都与root匹配,则假定两个密钥都存在,则root是LCA。如果root与任何键都不匹配,我们递归左右子树。
  • 在左子树中具有一个密钥而在右子树中具有另一个密钥的节点是LCA。如果两个键都位于左子树中,则左子树也具有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)-空间利用了父指针哈希表,ancestor_set和queue,每个都为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;
    }
}

答案 18 :(得分:0)

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

答案 19 :(得分:0)

这里的一些解决方案假设存在对根节点的引用,一些假设树是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
    }

答案 20 :(得分:0)

没有父节点,带有遍历的解决方案会给你O(n)时间复杂度,这是正确的。

遍历式方法 假设您正在为节点A和B找到LCA,最直接的方法是首先获取从根到A的路径,然后获取从根到B的路径。一旦有了这两个路径,就可以轻松地迭代它们并找到它们最后一个公共节点,它是A和B的最低共同祖先。

递归解决方案 另一种方法是使用递归。首先,我们可以从左树和右树(如果存在)获得LCA。如果A或B中的任何一个是根节点,则根是LCA,我们只返回根,这是递归的终点。当我们将树分成子树时,最终我们将击中A和B.

要组合子问题解决方案,如果LCA(左树)返回一个节点,我们知道A和B都位于左侧树中,返回的节点是最终结果。如果LCA(左)和LCA(右)都返回非空节点,则意味着A和B分别位于左侧和右侧树中。在这种情况下,根节点是最低的公共节点。

检查Lowest Common Ancestor以获取详细的分析和解决方案。

答案 21 :(得分:0)

广度优先搜索的代码以确保两个节点都在树中。 然后才进行LCA搜索。 如果您有任何改进建议,请发表评论。 我想我们可以标记它们被访问并在我们离开的某个点重新开始搜索以改进第二个节点(如果没有找到VISITED)

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

答案 22 :(得分:0)

这是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)
  ...

答案 23 :(得分:0)

试试这个

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

答案 24 :(得分:0)

还有一种方法。然而,它不如答案中已经建议的那样有效。

  • 为节点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;       
    }

答案 25 :(得分:0)

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

Examine root node

if value1 and value2 are strictly less that the value at the root node 
    Examine left subtree
else if value1 and value2 are strictly greater that the value at the root node 
    Examine right subtree
else
    return root
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;
} 

答案 26 :(得分:0)

如果对伪代码(大学家庭作品)感兴趣的人就是其中一个。

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

答案 27 :(得分:0)

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

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

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

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

  3. 仅为完成(与问题无关),BST中的LCA(O(log(N))

  4. 测试

  5. <强>递归:

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

答案 28 :(得分:0)

这就是我的想法,

  1. 找到第一个节点的路由,将其存储到arr1。
  2. 开始寻找2节点的路由,同时检查从root到arr1的每个值。
  3. 值不同的时间,退出。旧的匹配值是LCA。
  4. 复杂性: 步骤1:O(n),步骤2 = ~O(n),总= = O(n)。

答案 29 :(得分:0)

我找到了解决方案

  1. 接受订单
  2. 预订
  3. 接受邮寄订单
  4. 根据3次遍历,您可以决定谁是LCA。 从LCA找到两个节点的距离。 添加这两个距离就是答案。

答案 30 :(得分:-1)

Php中的代码我假设树是一个数组二叉树。因此,您甚至不需要树来计算LCA。 输入:两个节点的索引 输出:LCA指数

    <?php
 global $Ps;

function parents($l,$Ps)
{

    if($l % 2 ==0)
        $p = ($l-2)/2;
    else            
        $p = ($l-1)/2;

    array_push($Ps,$p);     
    if($p !=0)
        parents($p,$Ps);

    return $Ps; 
}
function lca($n,$m)
{
    $LCA = 0;
    $arr1 = array();
    $arr2 = array();
    unset($Ps); 
$Ps = array_fill(0,1,0);
    $arr1 = parents($n,$arr1);
    unset($Ps); 
$Ps = array_fill(0,1,0);
    $arr2 = parents($m,$arr2);

    if(count($arr1) > count($arr2))
        $limit = count($arr2);
    else
        $limit = count($arr1);

    for($i =0;$i<$limit;$i++)
    {
        if($arr1[$i] == $arr2[$i])
        {
            $LCA = $arr1[$i];
            break;
        }
    }
    return $LCA;//this is the index of the element in the tree

}

var_dump(lca(5,6));
?>

请告诉我是否有任何缺点。

答案 31 :(得分:-1)

//如果两个值都小于当前节点,则遍历左子树 //或者如果两个值都大于当前节点,则遍历右侧子树 //或者LCA是当前节点

public BSTNode findLowestCommonAncestor(BSTNode currentRoot, int a, int b){
    BSTNode commonAncestor = null;
    if (currentRoot == null) {
        System.out.println("The Tree does not exist");
        return null;
    }

    int currentNodeValue = currentRoot.getValue();
    //If both the values are less than the current node then traverse the left subtree
    //Or If both the values are greater than the current node then traverse the right subtree
    //Or LCA is the current node
    if (a < currentNodeValue && b < currentNodeValue) {
        commonAncestor = findLowestCommonAncestor(currentRoot.getLeft(), a, b);
    } else if (a > currentNodeValue && b > currentNodeValue) {
        commonAncestor = findLowestCommonAncestor(currentRoot.getRight(), a, b);
    } else {
        commonAncestor = currentRoot;
    }

    return commonAncestor;
}

答案 32 :(得分:-2)

public class LeastCommonAncestor {

    private TreeNode root;

    private static class TreeNode {
        TreeNode left;
        TreeNode right;
        int item;

        TreeNode (TreeNode left, TreeNode right, int item) {
            this.left = left;
            this.right = right;
            this.item = item;
        }
    }

    public void createBinaryTree (Integer[] arr) {
        if (arr == null)  {
            throw new NullPointerException("The input array is null.");
        }

        root = new TreeNode(null, null, arr[0]);

        final Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.add(root);

        final int half = arr.length / 2;

        for (int i = 0; i < half; i++) {
            if (arr[i] != null) {
                final TreeNode current = queue.poll();
                final int left = 2 * i + 1;
                final int right = 2 * i + 2;

                if (arr[left] != null) {
                    current.left = new TreeNode(null, null, arr[left]);
                    queue.add(current.left);
                }
                if (right < arr.length && arr[right] != null) {
                    current.right = new TreeNode(null, null, arr[right]);
                    queue.add(current.right);
                }
            }
        }
    }

    private static class LCAData {
        TreeNode lca;
        int count;

        public LCAData(TreeNode parent, int count) {
            this.lca = parent;
            this.count = count;
        }
    }


    public int leastCommonAncestor(int n1, int n2) {
        if (root == null) {
            throw new NoSuchElementException("The tree is empty.");
        }

        LCAData lcaData = new LCAData(null, 0);
       // foundMatch (root, lcaData, n1,  n2);

        /**
         * QQ: boolean was returned but never used by caller.
         */
        foundMatchAndDuplicate (root, lcaData, n1,  n2, new HashSet<Integer>());

        if (lcaData.lca != null) {
            return lcaData.lca.item;
        } else {
            /**
             * QQ: Illegal thrown after processing function.
             */
            throw new IllegalArgumentException("The tree does not contain either one or more of input data. ");
        }
    }

//    /**
//     * Duplicate n1, n1         Duplicate in Tree
//     *      x                           x               => succeeds
//     *      x                           1               => fails.
//     *      1                           x               => succeeds by throwing exception
//     *      1                           1               => succeeds
//     */
//    private boolean foundMatch (TreeNode node, LCAData lcaData, int n1, int n2) {
//        if (node == null) {
//            return false;
//        }
//
//        if (lcaData.count == 2) {
//            return false;
//        }
//
//        if ((node.item == n1 || node.item == n2) && lcaData.count == 1) {
//            lcaData.count++;
//            return true;
//        }
//
//        boolean foundInCurrent = false;
//        if (node.item == n1 || node.item == n2) {
//            lcaData.count++;
//            foundInCurrent = true;
//        }
//
//        boolean foundInLeft = foundMatch(node.left, lcaData, n1, n2);
//        boolean foundInRight = foundMatch(node.right, lcaData, n1, n2);
//
//        if ((foundInLeft && foundInRight) || (foundInCurrent && foundInRight) || (foundInCurrent && foundInLeft)) {
//            lcaData.lca = node;
//            return true; 
//        }
//        return foundInCurrent || (foundInLeft || foundInRight);
//    }


    private boolean foundMatchAndDuplicate (TreeNode node, LCAData lcaData, int n1, int n2, Set<Integer> set) {
        if (node == null) {
            return false;
        }

        // when both were found
        if (lcaData.count == 2) {
            return false;
        }

        // when only one of them is found
        if ((node.item == n1 || node.item == n2) && lcaData.count == 1) {
            if (!set.contains(node.item)) {
                lcaData.count++;
                return true;
            }
        }

        boolean foundInCurrent = false;  
        // when nothing was found (count == 0), or a duplicate was found (count == 1)
        if (node.item == n1 || node.item == n2) {
            if (!set.contains(node.item)) {
                set.add(node.item);
                lcaData.count++;
            }
            foundInCurrent = true;
        }

        boolean foundInLeft = foundMatchAndDuplicate(node.left, lcaData, n1, n2, set);
        boolean foundInRight = foundMatchAndDuplicate(node.right, lcaData, n1, n2, set);

        if (((foundInLeft && foundInRight) || 
                (foundInCurrent && foundInRight) || 
                    (foundInCurrent && foundInLeft)) &&
                        lcaData.lca == null) {
            lcaData.lca = node;
            return true; 
        }
        return foundInCurrent || (foundInLeft || foundInRight);
    }




    public static void main(String args[]) {
        /**
         * Binary tree with unique values.
         */
        Integer[] arr1 = {1, 2, 3, 4, null, 6, 7, 8, null, null, null, null, 9};
        LeastCommonAncestor commonAncestor = new LeastCommonAncestor();
        commonAncestor.createBinaryTree(arr1);

        int ancestor = commonAncestor.leastCommonAncestor(2, 4);
        System.out.println("Expected 2, actual " + ancestor);

        ancestor = commonAncestor.leastCommonAncestor(2, 7);
        System.out.println("Expected 1, actual " +ancestor);

        ancestor = commonAncestor.leastCommonAncestor(2, 6);
        System.out.println("Expected 1, actual " + ancestor);

        ancestor = commonAncestor.leastCommonAncestor(2, 1);
        System.out.println("Expected 1, actual " +ancestor);

        ancestor = commonAncestor.leastCommonAncestor(3, 8);
        System.out.println("Expected 1, actual " +ancestor);

        ancestor = commonAncestor.leastCommonAncestor(7, 9);
        System.out.println("Expected 3, actual " +ancestor);

        // duplicate request 
        try {
            ancestor = commonAncestor.leastCommonAncestor(7, 7);
        } catch (Exception e) {
            System.out.println("expected exception");
        }

        /**
         * Binary tree with duplicate values.
         */
        Integer[] arr2 = {1, 2, 8, 4, null, 6, 7, 8, null, null, null, null, 9};
        commonAncestor = new LeastCommonAncestor();
        commonAncestor.createBinaryTree(arr2);

        // duplicate requested
        ancestor = commonAncestor.leastCommonAncestor(8, 8);
        System.out.println("Expected 1, actual " + ancestor);

        ancestor = commonAncestor.leastCommonAncestor(4, 8);
        System.out.println("Expected 4, actual " +ancestor);

        ancestor = commonAncestor.leastCommonAncestor(7, 8);
        System.out.println("Expected 8, actual " +ancestor);

        ancestor = commonAncestor.leastCommonAncestor(2, 6);
        System.out.println("Expected 1, actual " + ancestor);

         ancestor = commonAncestor.leastCommonAncestor(8, 9);  
        System.out.println("Expected 8, actual " +ancestor); // failed.
    }
}
相关问题