Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/11.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Algorithm 如何在二叉树中查找节点的第一个公共祖先?_Algorithm_Binary Tree_Least Common Ancestor - Fatal编程技术网

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)
中找到此路径。