如何在任何二叉树中找到两个节点的最低共同祖先?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在任何二叉树中找到两个节点的最低共同祖先?相关的知识,希望对你有一定的参考价值。
这里的二叉树可能不一定是二进制搜索树。 结构可以视为 -
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),因为它们只不过是数组中的简单迭代)。但这很有可能是错误的。 :-)
但这是一个非常粗略的方法,我不确定它是否会因某些情况而崩溃。这个问题还有其他(可能是更优的)解决方案吗?
尼克约翰逊是正确的,如果你没有父指针,你可以做的最好的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);
只要两个给定的节点(例如root
和p
,其中必须找到祖先)在同一个子树中(意味着它们的值都比较小或者都大于根的),只需从整个树的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;
它是如何工作的
- 得到表示x和y所需的位,使用二进制搜索是O(log(32))
- x&y的二进制表示法的公共前缀是共同的祖先
- 通过较大的比特数表示的任何一个由k >> diff引入相同的比特
- k = x ^ y擦除x和y的公共前缀
- 找到代表剩余后缀的位
- 通过后缀位移动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会在二叉树中找到两个节点的最低共同祖先