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


187

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

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

我可以和一个朋友一起解决的最大解决方案就是这种情况-
考虑这个二叉树

二叉树

有序遍历收益-8,4,9,2,5,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),因为它们仅是数组中的简单迭代)。但是很有可能这是错误的。:-)

但这是一种非常粗糙的方法,我不确定在某些情况下它是否会崩溃。是否还有其他(可能是最佳的)解决方案?


6
出于好奇,这有什么实际用途?
大卫·布鲁内尔

19
@David:LCA查询回答非常有用。LCA +后缀树=强大的字符串相关算法。

44
当我问一个类似的问题时,它被诸如面试问题之类的评论否决了。SO的二重性?:(
some_other_guy

5
@Siddant +1了解问题中提供的详细信息。:)
13年

5
@DavidBrunelle计算LCA的一个实际应用:这是呈现网页时的必要计算,尤其是在计算适用于特定DOM元素的级联样式表(CSS)时。
zc22 2014年

Answers:


74

尼克·约翰逊是正确的,一个一个O(n)的时间复杂度算法是最好的,如果你没有父指针,你可以做。)对于算法的一个简单的递归版本中看到代码金丁的职务它运行在O(n)的时间。

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

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

对您所讨论的其他节点执行相同的操作,导致(未显示步骤):{1,2}

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

该算法需要O(h)时间,其中h是树的高度。在最坏的情况下,O(h)等于O(n),但是如果树是平衡的,则只有O(log(n))。它还需要O(h)空间。可能只使用恒定空间的改进版本,代码在CEGRD的帖子中显示


不管树是如何构造的,如果这是您对树执行多次操作而又不改变树之间的操作,则可以使用其他算法,这些算法需要O(n)[线性]时间准备,但是要找到配对仅需O(1)[恒定]时间。有关这些算法的参考,请参阅Wikipedia上最低的祖先问题页面。(感谢Jason最初发布了此链接)


1
如果给出了父指针,那就可以了。树中的节点与我在问题中给出的结构相同-仅左/右子指针,没有父指针。如果没有可用的父指针,并且树不是二叉搜索树,而只是二叉树,是否有O(log(n))解决方案?
Siddhant

2
如果您没有找到父节点和给定节点之间路径的特定方法,那么平均大约需要O(n)时间才能找到该路径。那将使得不可能有O(log(n))时间。但是,如果您要多次执行此操作而不更改其间的树,则使用O(1)对查找的O(n)一次性成本可能是您最好的选择。否则,应尽可能添加父指针。它可以使很多潜在的算法更快,但是我很确定它不会改变任何现有算法的顺序。希望这可以帮助。
凯文·卡斯卡特

1
这种方法可以用O(1)内存中完成-见Artelius的(和其他人)解决方案在stackoverflow.com/questions/1594061/...
汤姆Sirgedas

@Tom:的确,对于基于列表的算法,这可以将内存复杂度限制为O(1)。显然,这意味着对树的每一侧进行一次迭代以获取节点的深度,然后第二次(部分)进行迭代以找到共同祖先。对于具有父指针而不进行O(n)预计算的情况,O(h)时间和O(1)空间显然是最佳的。
凯文·卡斯卡特

1
@ALBI O(h)O(log(n))在树平衡时使用。对于任何树,无论是二进制还是非二进制,如果您有父指针,您都可以随O(h)时间跟踪父指针,从而确定从叶到根的路径h。这为您提供了从叶到根的路径。如果路径存储为堆栈,则迭代堆栈将为您提供从根到叶的路径。如果缺少父指针,并且没有树的特殊结构,那么找到从根到叶的路径确实需要O(n)时间。
凯文·卡斯卡特2013年

108

root节点开始,然后向下移动(如果找到任何具有pq作为其直接子节点的节点),则为LCA。(编辑-这应该是如果p或者q是节点的值,返回它,否则当一个会失败。p或者q是其他的直接孩子。)

否则,如果找到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;
                }
        }
}

行动中的代码


2
优雅的解决方案,但root == p || root == q =>返回根位似乎过于乐观。如果事实证明根是p / q,但另一个寻找的节点实际上不在树中怎么办?
伊恩·杜肯

15
我猜想,当p或q是不在二叉树中的值时,此代码将失败。我对吗?例如LCA(8,20)。ur代码返回8。但是二进制树中不存在
javaMan 2011年

3
该解决方案的成本是多少?有效率吗?即使找到了p和q,它似乎仍在继续搜索。是否因为p和q不是BST并且可能包含重复项而使树中的p和q可能不唯一?
MikeB 2013年

3
@MikeB,此解决方案肯定是O(n),因为在最坏的情况下,您仅遍历每个节点一次。Peter Lee,这是最有效的方法,无需使用父指针。您有更好的解决方案吗?
gsingh2011 2014年

8
应该删除第一个不完美的解决方案,以免分散注意力
Zizi Xing 2014年

50

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

4
当树中不存在节点时,这将不起作用。
Pratik Khadloya

如果给定的树是BST,您会优化代码吗?
蒙娜·贾拉

1
“如果根是a或b之一,则它是LCA。” 这可能不正确。您现在所知道的是,您无需检查其任何子级即可找到LCA。发生这种情况的原因是,我们以后可以检查根的父级是否在两个分支上都匹配(LCA是父级),或者只是其中一个(在这种情况下,一个可能是LCA,或者甚至更大的祖先可能是LCA) )。
andresp

28

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

如果您有一棵很深的树,那么这两种方法都可能失败。

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

这是代码:

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;

6
通常,二叉树没有对父元素的引用。可以添加父引用而没有任何问题,但是我会考虑O(n)辅助空间。
John Kurlak

此解决方案中有一个微妙的假设。如果一个节点是另一个节点的直接或间接父节点(即,较深的节点位于以较浅节点为根的树中),则此解决方案将返回较浅节点的父节点作为结果。根据您定义最低共同祖先的方式,这可能不是您想要的。一些定义将要求较浅的节点本身成为父节点。在这种情况下,您需要跟踪哪个是较浅的节点并将其返回。
斯里肯斯

8

好吧,这种类型取决于您的二叉树的结构。假定您有某种方法可以找到给定树根的所需叶节点-只需将其应用于两个值,直到选择的分支发散即可。

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


8

可以在以下位置找到它:-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根本不存在q,您的代码将如何表现?同样,p和q都不存在。谢谢!!!
试用

在时间上最大的O是什么?我认为是O(n * log(n)),两个很慢。
李·彼得(Peter Lee)


6

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

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

这将适用于二进制搜索树。


2
他明确表示该树不一定是BST。
李·彼得(Peter Lee)

@Peter Lee-即使对任何二叉树进行了简单的更改,上述逻辑也可以使用。代替给定节点的二进制搜索,请应用线性搜索(即,任何遍历,但两种情况都应相同)。当然,课外运行时间将是O(n)而不是O(logn)。实际上,当父指针不可用时,该算法是最健壮的算法。当给定节点之一不属于该树时,许多人(即“ codaddict”)给出的递归算法将不起作用
KGhatak


3

下面的递归算法将在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);
}

3

刚刚从整个树的走root只要双方给出的节点,说pq必须找到其祖先的都在同一子树中(这意味着它们的值都小于或都大于根)。

它从根部直接走到最小共同祖先,而不看树的其余部分,因此它的速度几乎与它一样快。几种方法可以做到这一点。

O(1)迭代空间

蟒蛇

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)

递归的

蟒蛇

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

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

2

考虑这棵树 在此处输入图片说明

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

后订单=> 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是答案


2

如果它是具有节点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&y所需的位,使用二进制搜索为O(log(32))
  2. x和y的二进制表示法的公共前缀是公共祖先
  3. 以较大的位数表示的位数以k >> diff表示为相同的位数
  4. k = x ^ y擦除x和y的公共前缀
  5. 查找代表剩余后缀的位
  6. 将x或y移位后缀位以获得通用前缀,即通用祖先。

之所以可行,是因为基本上将较大的数递归除以2,直到两个数相等为止。这个数字是共同的祖先。划分实际上是正确的轮班表现。因此,我们需要找到两个数字的公共前缀才能找到最接近的祖先


2

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

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

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

0

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

检查根节点

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

6
不是BST!
李·彼得(Peter Lee)

0

我找到了解决方案

  1. 按顺序
  2. 接受预订
  3. 接受订单

根据3个遍历,您可以确定谁是LCA。从LCA中找到两个节点的距离。将这两个距离相加,即可得出答案。


0

我是这样想的

  1. 找到第一个节点的路由,并将其存储在arr1上。
  2. 开始查找2节点的路由,同时检查从根到arr1的每个值。
  3. 值不同的时间,退出。旧的匹配值是LCA。

复杂度:步骤1:O(n),步骤2 =〜O(n),总计=〜O(n)。


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. 测验

递归:

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

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

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


}

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

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

0

粗略方式:

  • 在每个节点
    • 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;
   }
}

0

进行广度优先搜索的代码,以确保两个节点都在树中。然后,继续进行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;
}
}

0

您是正确的,如果没有父节点,则遍历的解决方案将给您O(n)时间复杂度。

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

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

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

检查最低公共祖先以获取详细的分析和解决方案。


0

这里的一些解决方案假定存在对根节点的引用,某些解决方案假定树是BST。使用哈希图共享我的解决方案,而无需引用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
    }

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;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.