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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在任何二叉树中找到两个节点的最低共同祖先?相关的知识,希望对你有一定的参考价值。

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

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

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

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),因为它们只不过是数组中的简单迭代)。但这很有可能是错误的。 :-)

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

答案

尼克约翰逊是正确的,如果你没有父指针,你可以做的最好的O(n)时间复杂度算法。)对于该算法的简单递归版本,请参阅在O(n)时间运行的Kinding's post中的代码。

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

所以对于你的例子中的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)中运行,以获得平衡的二叉树。如果传入getLCA()函数的任何一个节点与根相同,则根将是LCA,并且不需要执行任何recussrion。

测试用例。 [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);

另一答案

只要两个给定的节点(例如rootp,其中必须找到祖先)在同一个子树中(意味着它们的值都比较小或者都大于根的),只需从整个树的q向下走。

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

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

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)

递归

蟒蛇

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

另一答案

在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


另一答案
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

另一答案

考虑这棵树

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

邮购=> 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

在后序中我们有=> 9,14,15,13,​​12,7在8和11之后预订我们在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是答案

另一答案

如果它是完整的二叉树,节点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,直到两个数字相等。这个数字是共同的祖先。划分实际上是正确的转变操作。所以我们需要找到两个数字的公共前缀来找到最近的祖先

另一答案

这是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,
  

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

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

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

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

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

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

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