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

190

这里的二叉树不一定是二叉搜索树。
可以将其结构表示为 -

struct node {
    int data;
    struct node *left;
    struct node *right;
};
我和我的朋友想到的最优解如下:
考虑这个二叉树

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),因为它们仅是数组中的简单迭代)。但很有可能我错了。:-)

但这只是一个非常粗略的方法,我不确定是否有一些情况会失效。还有其他(可能更优)的解决方案吗?


6
出于好奇,这个有什么实际用途? - David Brunelle
19
LCA查询回答非常有用。LCA+后缀树=强大的字符串相关算法。 - Aryabhatta
44
当我问了类似的问题时,它被投票下降,并且有评论说这是一道面试题。这就是 Stack Overflow 的二元性吗? :( - some_other_guy
5
谢谢你的赞赏!感谢你在问题中提供了详细信息。 :) - amod
5
计算最近公共祖先(LCA)的一个实际应用是在呈现网页时,在计算适用于特定DOM元素的级联样式表(CSS)时,LCA是必要的计算。 - zc22
显示剩余7条评论
34个回答

3

只需从整棵树的 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

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

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

2
考虑以下这棵树 enter image description here 。如果我们进行后序遍历和前序遍历,并找到第一个出现的共同祖先,我们就可以得到共同的祖先。
后序遍历 => 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。
在后序遍历中,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的最近公共祖先为7。
在后序遍历中,5 和 10 之后我们有 => 11,9,14,15,13,12,7 在前序遍历中,5 和 10 之前我们有 => 7,3,1,0,2,6,4 7 是第一个在后序遍历中出现在5和10之后,在前序遍历中出现在5和10之前的数字,因此7是答案。

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

如果它是一个满二叉树,其中节点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
两个节点node1和node2的最低公共祖先是树中同时拥有这两个节点作为后代的最低节点。
从根节点开始遍历二叉树,直到找到这两个节点。每次访问一个节点,就将其添加到一个字典(parent)中。一旦在二叉树中找到这两个节点,就使用字典获取node1的祖先并将其添加到一个集合(ancestors)中。对于node2也按照同样的方式进行操作。如果node2的祖先在node1的祖先集合中出现,则它们之间的第一个公共祖先就是该祖先。
下面是使用堆栈和字典实现的迭代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)


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

简单粗暴的方法:

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

虽然这个问题已经有答案了,但这是我使用C编程语言解决这个问题的方法。尽管代码显示了一个二叉搜索树(就插入而言),但该算法也适用于二叉树。思路是在中序遍历中遍历所有从节点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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接