C 在二叉树中查找共同祖先
这个问题是在一次采访中问我的:我有一棵二叉树,我必须找到该树的两个随机节点的共同祖先(父)。我还得到一个指向根节点的指针C 在二叉树中查找共同祖先,c,algorithm,binary-tree,binary-search-tree,C,Algorithm,Binary Tree,Binary Search Tree,这个问题是在一次采访中问我的:我有一棵二叉树,我必须找到该树的两个随机节点的共同祖先(父)。我还得到一个指向根节点的指针 我的答案是: private BinaryTreeNode LeastCommonAncestorUsingRecursion(BinaryTreeNode treeNode, int e1, int e2) { Debug.Assert(e1 != e2);
我的答案是:
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, e1, 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, e1, e2);
if (null == node)
{
throw new Exception(string.Format("Least common ancenstor not found for the given elements: {0},{1}", e1, e2));
}
return node;
}
public BinaryTreeNode LeastCommonAncestorUsingPaths(int e1, int e2)
{
var path1 = new List<BinaryTreeNode>();
var node1 = this.FindNodeAndPath(this._root, e1, 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;
}
分别遍历两个节点的树,直到到达预期的节点。
并行遍历时将元素和下一个地址存储在链表中。然后我们有两个链表。因此,请尝试比较两个链表,两个链表中最后一个公共节点是父节点
我认为这个解决方案是正确的,如果我错了,请纠正我。
如果这个解决方案是正确的,我想知道这是这个任务唯一更好的解决方案,还是还有其他更好的解决方案 进行级别顺序遍历,对于遇到的每个节点,我们检查其子节点。如果它们是提供的随机节点,则会找到祖先节点 EDIT1:
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, e1, 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, e1, e2);
if (null == node)
{
throw new Exception(string.Format("Least common ancenstor not found for the given elements: {0},{1}", e1, e2));
}
return node;
}
public BinaryTreeNode LeastCommonAncestorUsingPaths(int e1, int e2)
{
var path1 = new List<BinaryTreeNode>();
var node1 = this.FindNodeAndPath(this._root, e1, 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;
}
这是一个提纲
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);
}
更新 前面的算法只会找到公共父节点(直接祖先),因此,如果两个随机选择的节点不是公共父节点的子节点,则不会找到答案 下面的算法将找到共同的祖先,而不仅仅是父母 我认为以下算法会起作用: 对二叉树进行后序遍历,并查找随机节点1
r1
,如果找到,则在状态变量中将其标记为处于状态1,并继续查找第二个节点,如果找到,则将状态变量更新为状态2,并停止搜索并返回。每个节点都应该(递归地)将状态变量传递给其父节点。在状态2中遇到状态变量的第一个节点是公共祖先
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
该算法的实现如下:
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, r1, 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, d1, d2, n1, n2);
find(root->right, d1, d2, n1, n2);
}
LCA(root, d1, d2)
{
node *n1=null, *n2=null;
find(root, d1, d2, n1, 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是节点1的深度,因此是O(n)。第二个循环运行m次,其中m在节点2的深度。临时列表的查找(最坏情况下)为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)。在两个随机节点上设置一个指针。 通过遍历到顶部并计算与根节点的距离,找到每个节点的深度。 然后再次在两个节点上设置指针。对于更深的节点,向上遍历,直到两个指针位于同一深度。 然后向上遍历两个节点,直到指针指向同一节点。这就是祖先节点 “向上遍历”的意思是将指针移动到当前节点的父节点 编辑以澄清:关键思想是,当两个节点位于同一深度时,只需简单的遍历即可快速找到公共父节点。所以你爬到较低的一个,直到两个都在同一深度,然后你向上移动。 对不起,我不太懂C或者我会写代码,但那个算法应该能回答你的问题 再次编辑:我的方法在O(log(n))时间和O(1)内存中运行
另一个编辑:O(log(n))在平衡树中。对于不平衡树,最坏情况下的性能是O(n)。感谢@DaveCahill这个问题已经得到了很好的研究,有一些已知的算法可以在线性时间内解决它描述了许多可以用来解决此问题的不同方法。诚然,这是一篇研究论文,因此算法有点棘手,但它描述的一些方法实际上是相当可行的。可能是愚蠢的方法: 生成从每个节点到根节点的路径,并将其存储为“L”和“R”字符串 R
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, e1, e2);
if (null == node)
{
throw new Exception(string.Format("Least common ancenstor not found for the given elements: {0},{1}", e1, e2));
}
return node;
}
public BinaryTreeNode LeastCommonAncestorUsingPaths(int e1, int e2)
{
var path1 = new List<BinaryTreeNode>();
var node1 = this.FindNodeAndPath(this._root, e1, 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;
}
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, e, path);
}
if(n != null)
{
path.Insert(0, node);
return n;
}
return null;
}
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, 13, 2, 13, 18, 13, 5, 13, 18, 13, 13, 14, 18, 25, 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);
}
}