Algorithm 如何在二叉树中查找节点的第一个公共祖先?
下面是我找到第一个共同祖先的算法。但我不知道如何计算它的时间复杂度,有人能帮忙吗Algorithm 如何在二叉树中查找节点的第一个公共祖先?,algorithm,binary-tree,least-common-ancestor,Algorithm,Binary Tree,Least Common Ancestor,下面是我找到第一个共同祖先的算法。但我不知道如何计算它的时间复杂度,有人能帮忙吗 public Tree commonAncestor(Tree root, Tree p, Tree q) { if (covers(root.left, p) && covers(root.left, q)) return commonAncestor(root.left, p, q); if (covers(root.right, p) && co
public Tree commonAncestor(Tree root, Tree p, Tree q) {
if (covers(root.left, p) && covers(root.left, q))
return commonAncestor(root.left, p, q);
if (covers(root.right, p) && covers(root.right, q))
return commonAncestor(root.right, p, q);
return root;
}
private boolean covers(Tree root, Tree p) { /* is p a child of root? */
if (root == null) return false;
if (root == p) return true;
return covers(root.left, p) || covers(root.right, p);
}
好的,让我们从确定这个算法最坏的情况开始<代码>覆盖从左到右搜索树,因此如果要搜索的节点是最右边的叶,或者根本不在子树中,则会出现最坏的情况。此时,您将访问子树中的所有节点,因此
covers
是O(n),其中n是树中的节点数
类似地,common祖先
在p
和q
的第一个共同祖先位于树的右下方时,表现出最坏的行为。在这种情况下,它将首先调用covers
两次,这两种情况下的时间行为都最差。然后,它将在右子树上再次调用自己,在平衡树的情况下,它的大小为n/2
假设树是平衡的,我们可以通过递归关系T(n)=T(n/2)+O(n)
来描述运行时间。利用主定理,我们得到了平衡树的答案T(n)=O(n)
现在,如果树是不平衡的,那么在最坏的情况下,我们可能只会为每个递归调用将子树的大小减少1,从而产生递归T(n)=T(n-1)+O(n)
。解决这个问题的方法是T(n)=O(n^2)
不过,你可以做得更好。
例如,与其简单地用cover
确定哪个子树包含p
或q
,不如确定p
和q
的整个路径。这需要O(n)
就像封面一样,我们只是保留更多信息。现在,平行地穿过这些路径,并在它们分叉的地方停下来。这总是O(n)
如果您有从每个节点指向其父节点的指针,您甚至可以通过生成“自底向上”的路径来改进这一点,为平衡树提供O(logn)
请注意,这是一种时空折衷,因为当您的代码占用O(1)
空间时,此算法占用O(logn)
空间用于平衡树,并且通常占用O(n)
空间。如图所示,由于重复了许多操作,您的算法效率非常低
我将采用不同的方法:如果两个给定节点不在同一子树中(从而使其成为第一个公共祖先),我将确定从根到两个给定节点的路径,并比较节点,而不是测试每个潜在的根节点。从根向下的路径上的最后一个公共节点也是第一个公共祖先
下面是一个(未经测试的)Java实现:
private List<Tree> pathToNode(Tree root, Tree node) {
List<Tree> path = new LinkedList<Tree>(), tmp;
// root is wanted node
if (root == node) return path;
// check if left child of root is wanted node
if (root.left == node) {
path.add(node);
path.add(root.left);
return path;
}
// check if right child of root is wanted node
if (root.right == node) {
path.add(node);
path.add(root.right);
return path;
}
// find path to node in left sub-tree
tmp = pathToNode(root.left, node);
if (tmp != null && tmp.size() > 1) {
// path to node found; add result of recursion to current path
path = tmp;
path.add(0, node);
return path;
}
// find path to node in right sub-tree
tmp = pathToNode(root.right, node);
if (tmp != null && tmp.size() > 1) {
// path to node found; add result of recursion to current path
path = tmp;
path.add(0, node);
return path;
}
return null;
}
public Tree commonAncestor(Tree root, Tree p, Tree q) {
List<Tree> pathToP = pathToNode(root, p),
pathToQ = pathToNode(root, q);
// check whether both paths exist
if (pathToP == null || pathToQ == null) return null;
// walk both paths in parallel until the nodes differ
while (iterP.hasNext() && iterQ.hasNext() && iterP.next() == iterQ.next());
// return the previous matching node
return iterP.previous();
}
私有列表路径节点(树根、树节点){
列表路径=新建LinkedList(),tmp;
//根节点是需要的节点
if(root==节点)返回路径;
//检查根节点的左子节点是否需要
if(root.left==节点){
添加(节点);
add(root.left);
返回路径;
}
//检查是否需要根节点的右子节点
if(root.right==节点){
添加(节点);
add(root.right);
返回路径;
}
//在左子树中查找节点的路径
tmp=路径节点(root.left,node);
如果(tmp!=null&&tmp.size()>1){
//找到节点的路径;将递归结果添加到当前路径
路径=tmp;
添加路径(0,节点);
返回路径;
}
//在右子树中查找节点的路径
tmp=路径节点(root.right,node);
如果(tmp!=null&&tmp.size()>1){
//找到节点的路径;将递归结果添加到当前路径
路径=tmp;
添加路径(0,节点);
返回路径;
}
返回null;
}
公共树公共祖先(树根、树p、树q){
列表路径顶部=路径节点(根,p),
pathToQ=路径节点(根,q);
//检查两条路径是否都存在
if(pathToP==null | | pathToQ==null)返回null;
//平行行走两条路径,直到节点不同
而(iterP.hasNext()&&iterQ.hasNext()&&iterP.next()==iterQ.next());
//返回上一个匹配的节点
返回iterP.previous();
}
pathToNode
和common祖先
都在O(n)中。我有个问题。在你的声明中。。。让我们确定p
和q
的整个路径。这需要O(n)
就像封面一样。。。从根到节点p
的路径不应该采用O(logn)
而不是O(n)
?@Bhaskar。假设树大致平衡,路径的长度确实是O(logn)
,但查找此路径需要O(n)
,因为您必须从根搜索节点,它可能位于树中的任何位置,因此在最坏的情况下,您必须搜索所有节点。如果您有从节点指向其父节点的指针,您确实可以通过向上遍历在O(logn)
中找到此路径。