问题描述
|
这个问题在一次采访中被问到我:我有一棵二叉树,我必须找到给定该树的两个随机节点的共同祖先(父母)。我还得到了指向根节点的指针。
我的答案是:
分别遍历两个节点的树,直到到达预期的节点。
移动时并行将元素和下一个地址存储在链表中。然后,我们有了两个链接列表。因此,请尝试比较两个链表,两个链表中的最后一个公共节点是父节点。
我认为此解决方案是正确的,如果我错了,请纠正我。
如果该解决方案正确,那么我是否可以知道这是此任务的唯一更好的解决方案,或者还有其他更好的解决方案!
解决方法
也许愚蠢的做法:
生成从每个节点到根的路径,并将其存储为\“ L \”和\“ R \”的字符串。
反转这些字符串。使用最长的公共前缀-现在您具有通向共同祖先的路径。
, 在两个随机节点上都设置一个指针。
通过遍历顶部并计算与根节点的距离来找到每个节点的深度。
然后再次将指针设置在两个节点上。对于更深的节点,向上移动直到两个指针都位于同一深度。
然后向上遍历两个节点,直到指针指向同一节点。那是祖先节点。
通过“向上移动”,我的意思是将指针移动到当前节点的父节点。
编辑以阐明这一点:关键思想是,当两个节点处于相同深度时,您可以通过简单遍历很快地找到公共父级。因此,您爬下一个,直到两个都到达相同的深度,然后向上走。
抱歉,我不太了解C或我会写代码,但是该算法可以回答您的问题。
再次编辑:我的方法在O(log(n))时间和O(1)内存中运行。
另一个编辑:平衡树中的O(log(n))。对于不平衡树,最坏情况下的性能为O(n)。谢谢@DaveCahill
, 我认为您可以同时搜索两个节点。搜索的分歧点是共同祖先。
commonAncestor tree a b:
value := <value of node \'tree\'>
if (a < value) && (b < value)
then commonAncestor (left tree) a b
else if (a > value) && (b > value)
then commonAncestor (right tree) a b
else tree
有趣的是,这种方法可以扩展到两个以上的节点(检查所有节点是否位于tree
的左侧,等等)。
, 进行级别顺序遍历,对于遇到的每个节点,我们都会检查其子级。如果它们是提供的随机节点,那么将找到祖先节点。
编辑1:
这是一个大纲
struct _node {
my_type data;
struct _node *left;
struct _node *right;
}
q = queue_create ();
queue_insert (q,head);
temp = head;
while (!empty (q))
{
temp = queue_remove (q);
if (
(temp->left == my_random_node_1) && (head->right == my_random_node_2) ||
(temp->left == my_random_node_2) && (head->right == my_random_node_1)
)
{
/* temp is the common parent of the two target notes */
/* Do stuffs you need to do */
}
/* Enqueue the childs,so that in successive iterations we can
* check them,by taking out from the queue
*/
push (q,temp->left);
push (q,temp->right);
}
更新
先前的算法只会找到共同的父母(直接祖先),因此,如果两个随机选择的节点不是共同的父母的孩子,那么将找不到答案。
以下算法将找到共同的祖先,而不仅仅是父母。
我认为以下算法会起作用:
进行二叉树的后遍历,找到随机节点1r1
,如果找到它,则将其标记为状态变量,处于状态一,继续寻找第二个节点,如果找到,则更新状态变量陈述两个,然后停止搜索更多并返回。每个节点都应将状态变量传递给其父节点(递归)。在状态2中遇到状态变量的第一个节点是公共祖先。
该算法的实现如下:
int postorder (node *p,int r1,int r2)
{
int x = 0; /* The state variable */
if (p->data == TERMINAL_VAL)
return x;
/* 0x01 | 0x02 = 0x03 threfore
* state one is when x = 0x01 or x = 0x02
* state two is when x = 0x03
*/
if (p->data == r1)
x |= 0x01;
else if (p->data == r2)
x |= 0x02;
/* if we have x in state two,no need to search more
*/
if (x != 0x03)
x |= postorder (p->left,r1,r2);
if (x != 0x03)
x |= postorder (p->right,r2);
/* In this node we are in state two,print node if this node
* is not any of the two nodes r1 and r2. This makes sure that
* is one random node is an ancestor of another random node
* then it will not be printed instead its parent will be printed
*/
if ((x == 0x03) && (p->data != r1) && (p->data != r2))
{
printf (\"[%c] \",p->data);
/* set state variable to 0 if we do not want to print
* the ancestors of the first ancestor
*/
x = 0;
}
/* return state variable to parent
*/
return x;
}
我认为这将正常工作,尽管我仍然需要证明算法的正确性。有一个缺点,即如果一个节点是另一节点的子节点,则它将仅打印作为另一节点的父节点的节点,而不是打印它们的父节点。如果随机节点中的一个是另一个随机节点的祖先,那么它将打印父节点而不是打印祖先随机节点。在一个随机节点是根节点的情况下,它不会打印任何内容,因为它始终是另一个随机节点的祖先,因此它们的公共祖先不存在。在这种特殊情况下,该函数将在main
中返回0x03
,并且可以检测到它。
由于此算法执行后遍历,因此需要O(n)执行时间,因此需要O(n)内存。同样,一旦找到两个节点,搜索就停止了,节点越浅,搜索结束的速度就越快。
更新
这里是一些模式讨论:如何在任何二叉树中找到两个节点的最低公共祖先?
,这个问题已经被很好地研究,并且有已知的算法可以在线性时间内解决它。本文介绍了许多可用于解决问题的方法。诚然,这是一篇研究论文,因此算法有些棘手,但是它描述的某些方法实际上是相当可行的。
, @以上,这将不起作用,因为您假设两个节点都是某个特定节点的直接子节点...
8
10 12
7
我将节点设为7和12,答案必须为8。
让我们这样
find(root,d1,d2,n1=null,n2=null)
{
if(n1 && n2) return;
if(!root) return;
else if(root -> d == d1 ) n1 = root;
else if(root -> d == d2 ) n2 = root;
find(root->left,n1,n2);
find(root->right,n2);
}
LCA(root,d2)
{
node *n1=null,*n2=null;
find(root,n2);
if(n1 == null || n2 == null )error \'nodes not present\' exit(0);
findIntersect(n1,n2);
}
findInterSect(node *n1,node *n2)
{
l1 = length(n1);
l2 = length(n2);
node *g = n2,*l = n1;
diff = abs(l1 - l2);
if(l1>l2) g = n1 l =n2
while(diff) g = g->parent; diff--;
// now both nodes are at same level
while(g != l) g= g->parent,l = l->parent;
}
, 伪代码:
node *FindCommonAncestor(node *root,node *node1,node *node2) {
node *current = node1;
node_list temp_list;
temp_list.add(current);
while (current != root) {
current = current.parent;
temp_list.add(current);
}
current = node2;
while (current not in temp_list) {
current = current.parent;
}
return current;
}
如果节点肯定是同一棵树的一部分,那么它们肯定会有一个共同的祖先(即使在最坏的情况下它是根)。因此它将始终终止,并且没有错误条件值得担心。
第一个循环运行n次,其中n是node1的深度,因此它是O(n)。第二个循环运行m次,其中node2的深度为m。在临时列表中的查找为(最坏情况)为n。因此,第二个循环是O(m * n),它占主导地位,因此该函数在O(m * n)中运行。
如果您为临时空间而不是列表使用良好的集合数据结构(例如哈希表),则可以将查找切割为(通常)为O(1),而不会增加向临时节点添加节点的成本。这将我们的函数时间减少到O(m)。
两种方式的空间要求均为O(n)。
由于我们不知道n和m的提前时间,因此将其以树中节点的总数表示:S。如果树是平衡的,则n和m分别由log_2( S),因此运行时间为O(log_2(S)^ 2)。 Log_2非常强大,因此在担心2的幂之前,S必定会变得很大。如果树不平衡,那么我们将丢失log_2(树实际上可能会退化为链接列表)。因此,绝对最差情况(当一个节点是根,而另一个节点是完全退化的树的叶子时)是O(S ^ 2)。
, 您好,这将返回最低的祖先节点值,其中将传递树的根和val1,val2->节点的数据值
int CommonAncestor(node *root,int val1,int val2)
{
if(root == NULL || (! root->left && ! root->right )
return false;
while(root)
{
if(root->data < val1 && root->data < val2)
{
root = root->left;
}
else if(root->data > val1 && root->data > val2)
{
root= root->right;
}
else
return root->data;
}
}
,
除非满足任意一个节点,否则进行预遍历,并保存直到现在为止访问的节点。
顺序遍历,当满足(提供的两个节点中的任意一个)节点中的任意一个时,开始保存节点,并保存列表,直到遇到下一个节点。
后遍历,当两个节点都被访问时开始保存节点...
一种
B C
D E F G
H I J K L M N O
假设H和E是两个随机节点。
脱氧核糖核酸
高迪
埃布门诺加
找到这三个节点中共有的第一个节点...
, 以下是c#(.net)(上面已讨论)中的两种方法供参考:
在二叉树中查找LCA的递归版本(O(N)-最多访问每个节点)
(解决方案的要点是LCA是(a)两个树中只有两个元素都位于子树两侧(左右)的二叉树中的节点是LCA。(b)同样,存在哪个节点也没有关系方面-最初我试图保留这些信息,显然递归函数变得如此混乱,一旦我意识到,它就变得非常优雅。
搜索两个节点(O(N)),并跟踪路径(使用额外的空间-因此,即使二叉树平衡良好,#1可能还是比较优越的,因为这样一来,因为额外的内存消耗将在O(log(N))。
以便比较路径(与接受的答案基本类似-但通过假设二叉树节点中不存在指针节点来计算路径)
只是为了完成(与问题无关),BST中的LCA(O(log(N))
测验
递归:
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,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,e2);
if (null == node)
{
throw new Exception(string.Format(\"Least common ancenstor not found for the given elements: {0},{1}\",e2));
}
return node;
}
通过跟踪两个节点的路径的解决方案:
public BinaryTreeNode LeastCommonAncestorUsingPaths(int e1,int e2)
{
var path1 = new List<BinaryTreeNode>();
var node1 = this.FindNodeAndPath(this._root,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,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,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);
}
}