这里的二叉树不一定是二叉搜索树。
该结构可以被视为 -
struct node {
int data;
struct node *left;
struct node *right;
};
我可以与朋友一起制定的最大解决方案是这样的 -
考虑这个二叉树:
中序遍历产生 - 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),因为它们只不过是数组中的简单迭代)。但这种说法很可能是错误的。 :-)
但这是一种非常粗糙的方法,我不确定它是否在某些情况下会失败。这个问题还有其他(可能更优化)的解决方案吗?
The Binary Tree here is may not necessarily be a Binary Search Tree.
The structure could be taken as -
struct node {
int data;
struct node *left;
struct node *right;
};
The maximum solution I could work out with a friend was something of this sort -
Consider this binary tree :
The inorder traversal yields - 8, 4, 9, 2, 5, 1, 6, 3, 7
And the postorder traversal yields - 8, 9, 4, 5, 2, 6, 7, 3, 1
So for instance, if we want to find the common ancestor of nodes 8 and 5, then we make a list of all the nodes which are between 8 and 5 in the inorder tree traversal, which in this case happens to be [4, 9, 2]. Then we check which node in this list appears last in the postorder traversal, which is 2. Hence the common ancestor for 8 and 5 is 2.
The complexity for this algorithm, I believe is O(n) (O(n) for inorder/postorder traversals, the rest of the steps again being O(n) since they are nothing more than simple iterations in arrays). But there is a strong chance that this is wrong. :-)
But this is a very crude approach, and I'm not sure if it breaks down for some case. Is there any other (possibly more optimal) solution to this problem?
发布评论
评论(30)
如果它是完整二叉树,节点 x 的子节点为 2*x 和 2*x+1,那么有更快的方法
它是如何工作的
这是有效的,因为基本上递归地将较大的数字除以二,直到两个数字相等。这个数字就是共同的祖先。除法实际上是右移运算。所以我们需要找到两个数字的共同前缀来找到最近的祖先
If it is full binary tree with children of node x as 2*x and 2*x+1 than there is a faster way to do it
How does it work
This works because basically divide the larger number by two recursively until both numbers are equal. That number is the common ancestor. Dividing is effectively the right shift opearation. So we need to find common prefix of two numbers to find the nearest ancestor
在 Scala 中,您可以:
In scala, you can:
两个节点
node1
和node2
之间的最低公共祖先是树中两个节点都是后代的最低节点。从根节点开始遍历二叉树,直到找到两个节点。每次访问节点时,都会将其添加到字典(称为
parent
)中。一旦在二叉树中找到了两个节点,就可以使用字典获取
node1
的祖先并将其添加到一个集合中(称为祖先
)。对于
node2
,以相同的方式执行此步骤。如果node2
的祖先存在于为node1
设置的祖先中,则它是它们之间的第一个公共祖先。下面是使用 stack 和 dictionary 实现的迭代 Python 解决方案,其中包含以下几点:
node1
和node2
将存在于二叉树中这种方法的复杂度是:O(n)
The lowest common ancestor between two nodes
node1
andnode2
is the lowest node in a tree that has both nodes as descendants.The binary tree is traversed from the root node, until both nodes are found. Every time a node is visited, it is added to a dictionary (called
parent
).Once both nodes are found in the binary tree, the ancestors of
node1
are obtained using the dictionary and added to a set (calledancestors
).This step is followed in the same manner for
node2
. If the ancestor ofnode2
is present in the ancestors set fornode1
, it is the first common ancestor between them.Below is the iterative python solution implemented using stack and dictionary with the following points:
node1
andnode2
will exist in the binary treeThe complexity of this approach is: O(n)
这是 C++ 的实现方法。已尽力使算法尽可能易于理解:
如何使用它:
Here is the C++ way of doing it. Have tried to keep the algorithm as much easy as possible to understand:
How to use it:
找到最低共同祖先的最简单方法是使用以下算法:
The easiest way to find the Lowest Common Ancestor is using the following algorithm:
我找到了一个解决方案
根据 3 次遍历,就可以决定谁是 LCA。
从 LCA 找到两个节点的距离。
将这两个距离相加,就是答案。
I found a solution
Depending on 3 traversals, you can decide who is the LCA.
From LCA find distance of both nodes.
Add these two distances, which is the answer.
这就是我的想法,
复杂性:
步骤 1 : O(n) ,步骤 2 =~ O(n) ,总计 =~ O(n)。
Here is what I think,
Complexity :
step 1 : O(n) , step 2 =~ O(n) , total =~ O(n).
以下是 c# (.net) 中的两种方法(均在上面讨论过)供参考:
在二叉树中查找 LCA 的递归版本(O(N) - 最多访问每个节点)
(解决方案的要点是 LCA 是二叉树中的唯一节点,其中两个元素都位于子树的两侧(左侧和右侧)是 LCA。(b) 而且无论哪一方存在哪个节点都并不重要 - 最初我试图保留该信息,显然递归函数在我意识到它后变得如此混乱,它变得非常优雅。
以便比较路径(本质上类似于接受的答案 - 但路径是通过假设指针节点不存在于二叉树节点中来计算的)
只是为了完成(与问题无关),BST 中的 LCA (O(log(N))
测试
递归:
其中通过以下公共方法调用上述私有递归版本:
通过跟踪两个节点的路径解决方案:
其中FindNodeAndPath 定义为
BST (LCA) - 不相关(仅供参考)
单元测试
Here are two approaches in c# (.net) (both discussed above) for reference:
Recursive version of finding LCA in binary tree (O(N) - as at most each node is visited)
(main points of the solution is LCA is (a) only node in binary tree where both elements reside either side of the subtrees (left and right) is LCA. (b) And also it doesn't matter which node is present either side - initially i tried to keep that info, and obviously the recursive function become so confusing. once i realized it, it became very elegant.
Searching both nodes (O(N)), and keeping track of paths (uses extra space - so, #1 is probably superior even thought the space is probably negligible if the binary tree is well balanced as then extra memory consumption will be just in O(log(N)).
so that the paths are compared (essentailly similar to accepted answer - but the paths is calculated by assuming pointer node is not present in the binary tree node)
Just for the completion (not related to question), LCA in BST (O(log(N))
Tests
Recursive:
where above private recursive version is invoked by following public method:
Solution by keeping track of paths of both nodes:
where FindNodeAndPath is defined as
BST (LCA) - not related (just for completion for reference)
Unit Tests
如果有人对伪代码(用于大学家庭作业)感兴趣,这里就是其中之一。
If someone interested in pseudo code(for university home works) here is one.
虽然这个问题已经得到解答,但这是我使用 C 编程语言解决这个问题的方法。虽然代码显示了二叉搜索树(就 insert() 而言),但该算法也适用于二叉树。这个想法是在中序遍历中遍历从节点 A 到节点 B 的所有节点,在后序遍历中查找这些节点的索引。后序遍历中索引最大的节点是最低公共祖先。
这是一个有效的 C 代码,用于实现在二叉树中查找最低公共祖先的函数。我还提供了所有实用函数等,但请跳转到 CommonAncestor() 以快速理解。
Although this has been answered already, this is my approach to this problem using C programming language. Although the code shows a binary search tree (as far as insert() is concerned), but the algorithm works for a binary tree as well. The idea is to go over all nodes that lie from node A to node B in inorder traversal, lookup the indices for these in the post order traversal. The node with maximum index in post order traversal is the lowest common ancestor.
This is a working C code to implement a function to find the lowest common ancestor in a binary tree. I am providing all the utility functions etc. as well, but jump to CommonAncestor() for quick understanding.
还可以有另一种方法。然而,它并不像答案中已经建议的那样有效。
为节点 n1 创建路径向量。
为节点 n2 创建第二个路径向量。
路径向量,表示从该节点将遍历以到达相关节点的集合节点。
路径
比较两个路径向量。它们不匹配的索引,返回该索引处的节点 - 1。这将给出 LCA。
这种方法的缺点:
需要遍历树两次来计算路径向量。
需要额外的 O(h) 空间来存储路径向量。
然而,这也很容易实现和理解。
计算路径向量的代码:
There can be one more approach. However it is not as efficient as the one already suggested in answers.
Create a path vector for the node n1.
Create a second path vector for the node n2.
Path vector implying the set nodes from that one would traverse to reach the node in question.
Compare both path vectors. The index where they mismatch, return the node at that index - 1. This would give the LCA.
Cons for this approach:
Need to traverse the tree twice for calculating the path vectors.
Need addtional O(h) space to store path vectors.
However this is easy to implement and understand as well.
Code for calculating the path vector:
尝试这样
Try like this
粗略方式:
或出于概括目的而正确。
上述方法的问题是我们将进行多次“查找”,即每个节点都有可能被遍历多次。
如果我们可以记录信息以便不再处理它,我们就可以克服这个问题(想想动态规划)。
因此,我们不是查找每个节点,而是记录已找到的内容。
更好的方法:
代码:
Crude way:
or right for the purposes of generalization.
The problem with the method above is that we will be doing the "find" multiple times, i.e. there is a possibility of each node getting traversed multiple times.
We can overcome this problem if we can record the information so as to not process it again (think dynamic programming).
So rather than doing find every node, we keep a record of as to whats already been found.
Better Way:
Code:
广度优先搜索的代码,以确保两个节点都在树中。
然后才能继续进行 LCA 搜索。
如果您有任何改进建议,请评论。
我认为我们可以将它们标记为已访问,并在我们停止的某个点重新开始搜索,以改进第二个节点(如果未找到已访问)
Code for A Breadth First Search to make sure both nodes are in the tree.
Only then move forward with the LCA search.
Please comment if you have any suggestions to improve.
I think we can probably mark them visited and restart the search at a certain point where we left off to improve for the second node (if it isn't found VISITED)
这里的一些解决方案假设存在对根节点的引用,一些假设树是 BST。
使用 hashmap 分享我的解决方案,不参考
root
节点,树可以是 BST 或非 BST:Some of the solutions here assumes that there is reference to the root node, some assumes that tree is a BST.
Sharing my solution using hashmap, without reference to
root
node and tree can be BST or non-BST:解决方案 1:递归 - 更快
解决方案 2:迭代 - 使用父指针 - 较慢
Solution 1: Recursive - Faster
Solution 2: Iterative - Using parent pointers - Slower
从
root
节点开始向下移动,如果您找到任何将p
或q
作为其直接子节点的节点,那么它就是LCA。 (编辑 - 这应该是如果p
或q
是节点的值,则返回它。否则,当p
或之一时,它将失败>q
是另一个的直接子节点。)否则,如果您找到一个节点,其右(或左)子树中有
p
且其左子树中有q
(或右)子树,那么它就是 LCA。固定代码如下所示:
当其中一个是另一个的直接子代时,以下代码将失败。
实际代码
Starting from
root
node and moving downwards if you find any node that has eitherp
orq
as its direct child then it is the LCA. (edit - this should be ifp
orq
is the node's value, return it. Otherwise it will fail when one ofp
orq
is a direct child of the other.)Else if you find a node with
p
in its right(or left) subtree andq
in its left(or right) subtree then it is the LCA.The fixed code looks like:
The below code fails when either is the direct child of other.
Code In Action
Nick Johnson 是正确的,如果没有父指针,O(n) 时间复杂度算法是最好的算法。)有关该算法的简单递归版本,请参阅 Kinding 的帖子,运行时间为 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 最初发布此链接)
Nick Johnson is correct that a an O(n) time complexity algorithm is the best you can do if you have no parent pointers.) For a simple recursive version of that algorithm see the code in Kinding's post which runs in O(n) time.
But keep in mind that if your nodes have parent pointers, an improved algorithm is possible. For both nodes in question construct a list containing the path from root to the node by starting at the node, and front inserting the parent.
So for 8 in your example, you get (showing steps): {4}, {2, 4}, {1, 2, 4}
Do the same for your other node in question, resulting in (steps not shown): {1, 2}
Now compare the two lists you made looking for the first element where the list differ, or the last element of one of the lists, whichever comes first.
This algorithm requires O(h) time where h is the height of the tree. In the worst case O(h) is equivalent to O(n), but if the tree is balanced, that is only O(log(n)). It also requires O(h) space. An improved version is possible that uses only constant space, with code shown in CEGRD's post
Regardless of how the tree is constructed, if this will be an operation you perform many times on the tree without changing it in between, there are other algorithms you can use that require O(n) [linear] time preparation, but then finding any pair takes only O(1) [constant] time. For references to these algorithms, see the the lowest common ancestor problem page on Wikipedia. (Credit to Jason for originally posting this link)
这是JAVA中的工作代码
Here is the working code in JAVA
到目前为止给出的答案使用递归或存储,例如内存中的路径。
如果树很深,这两种方法都可能会失败。
这是我对这个问题的看法。
当我们检查两个节点的深度(距根的距离)时,如果它们相等,那么我们可以安全地从两个节点向上移动到共同祖先。如果其中一个深度较大,那么我们应该从更深的节点向上移动,同时停留在另一个节点上。
代码如下:
该算法的时间复杂度为:O(n)。
该算法的空间复杂度为:O(1)。
关于深度的计算,我们可以先记住定义:如果v是根,则深度(v) = 0;否则,深度(v) = 深度(parent(v)) + 1。我们可以按如下方式计算深度:
The answers given so far uses recursion or stores, for instance, a path in memory.
Both of these approaches might fail if you have a very deep tree.
Here is my take on this question.
When we check the depth (distance from the root) of both nodes, if they are equal, then we can safely move upward from both nodes towards the common ancestor. If one of the depth is bigger then we should move upward from the deeper node while staying in the other one.
Here is the code:
The time complexity of this algorithm is: O(n).
The space complexity of this algorithm is: O(1).
Regarding the computation of the depth, we can first remember the definition: If v is root, depth(v) = 0; Otherwise, depth(v) = depth(parent(v)) + 1. We can compute depth as follows:
嗯,这取决于二叉树的结构。假设您有某种方法可以在给定树根的情况下找到所需的叶节点 - 只需将其应用于两个值,直到您选择的分支发散。
如果您没有办法在给定根的情况下找到所需的叶子,那么您唯一的解决方案 - 无论是在正常操作中还是找到最后一个公共节点 - 都是对树进行强力搜索。
Well, this kind of depends how your Binary Tree is structured. Presumably you have some way of finding the desired leaf node given the root of the tree - simply apply that to both values until the branches you choose diverge.
If you don't have a way to find the desired leaf given the root, then your only solution - both in normal operation and to find the last common node - is a brute-force search of the tree.
这可以在以下位置找到:-
http://goursaha.freeoda.com/DataStructure/LowestCommonAncestor.html
This can be found at:-
http://goursaha.freeoda.com/DataStructure/LowestCommonAncestor.html
Tarjan 的离线最不常见祖先算法 已经足够好了(参见维基百科)。 维基百科上有更多关于这个问题(最低共同祖先问题)的信息。
Tarjan's off-line least common ancestors algorithm is good enough (cf. also Wikipedia). There is more on the problem (the lowest common ancestor problem) on Wikipedia.
要找出两个节点的共同祖先: -
这适用于二叉搜索树。
To find out common ancestor of two node :-
This would work for binary search tree.
我尝试使用Java中的说明性图片和工作代码,
http://tech.bragboy.com/2010/02/least-common-ancestor-without-using.html
I have made an attempt with illustrative pictures and working code in Java,
http://tech.bragboy.com/2010/02/least-common-ancestor-without-using.html
对于平衡二叉树,下面的递归算法将以 O(log N) 运行。如果传入 getLCA() 函数的任何一个节点与根相同,则根将是 LCA,并且不需要执行任何递归。
测试用例。
[1] 两个节点 n1 和 n1 n2 在树中并位于其父节点的两侧。
[2] 节点 n1 或 n2 为根,LCA 为根。
[3]树中只有n1或n2,LCA将是树根的左子树的根节点,或者LCA将是树的右子树的根节点根。
[4] n1 和 n2 都不在树中,没有 LCA。
[5] n1 和 n2 彼此相邻在一条直线上,LCA 将是靠近树根的 n1 或 n2 中的一个。
The below recursive algorithm will run in O(log N) for a balanced binary tree. If either of the nodes passed into the getLCA() function are the same as the root then the root will be the LCA and there will be no need to perform any recussrion.
Test cases.
[1] Both nodes n1 & n2 are in the tree and reside on either side of their parent node.
[2] Either node n1 or n2 is the root, the LCA is the root.
[3] Only n1 or n2 is in the tree, LCA will be either the root node of the left subtree of the tree root, or the LCA will be the root node of the right subtree of the tree root.
[4] Neither n1 or n2 is in the tree, there is no LCA.
[5] Both n1 and n2 are in a straight line next to each other, LCA will be either of n1 or n2 which ever is closes to the root of the tree.
只要从整个树的
根
向下走,只要必须找到祖先的两个给定节点,例如p
和q
,都是在同一子树中(意味着它们的值都小于或都大于根的值)。它直接从根部走到最不常见的祖先,而不看树的其余部分,所以它几乎是尽可能快的。有几种方法可以做到这一点。
如果发生溢出,我会这样做 (root.val - (long)p.val) * (root.val - (long)q.val)
Just walk down from the whole tree's
root
as long as both given nodes ,sayp
andq
, for which Ancestor has to be found, are in the same sub-tree (meaning their values are both smaller or both larger than root's).This walks straight from the root to the Least Common Ancestor , not looking at the rest of the tree, so it's pretty much as fast as it gets. A few ways to do it.
in case of overflow, I'd do (root.val - (long)p.val) * (root.val - (long)q.val)
考虑这棵树
如果我们进行后序和前序遍历并找到第一个出现的共同前驱和后继,我们得到共同的祖先。
后序 => 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
后序中8,11 的最小共同祖先
=>9, 8 和 14,15,13,12,7 之后11
在预购中,我们在 8 和 之前有 =>7,3,1,0,2,6,4,5,12,9 11
9 是 8& 后面出现的第一个常见数字。后序中的 11 和 8 点之前前序为 11,因此 9 是答案
5,10 11,9,14,15,13,12,7 的最小共同祖先
后序中
前序中的 7,3,1,0,2,6,4
7 是后序中 5,10 之后、前序中 5,10 之前出现的第一个数字,因此 7 是答案
Consider this tree
If we do postorder and preorder traversal and find the first occuring common predecessor and successor, we get the common ancestor.
postorder => 0,2,1,5,4,6,3,8,10,11,9,14,15,13,12,7
preorder => 7,3,1,0,2,6,4,5,12,9,8,11,10,13,15,14
Least common ancestor of 8,11
in postorder we have = >9,14,15,13,12,7 after 8 & 11
in preorder we have =>7,3,1,0,2,6,4,5,12,9 before 8 & 11
9 is the first common number that occurs after 8& 11 in postorder and before 8 & 11 in preorder, hence 9 is the answer
Least common ancestor of 5,10
11,9,14,15,13,12,7 in postorder
7,3,1,0,2,6,4 in preorder
7 is the first number that occurs after 5,10 in postorder and before 5,10 in preorder, hence 7 is the answer