如何在二叉树中找到节点的第一个共同祖先?

时间:2011-05-11 11:54:19

标签: 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) && 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);
}

2 个答案:

答案 0 :(得分:9)

好的,让我们首先确定这种算法的最坏情况。 covers从左到右搜索树,因此如果您要搜索的节点是最右边的叶子,或者根本不在子树中,则会出现最坏情况。此时您将访问子树中的所有节点,因此covers O(n),其中 n 是树中的节点数

同样,当commonAncestorp的第一个共同祖先深入到树的右侧时,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)

但你可以做得更好。

例如,让我们确定pq的完整路径,而不是简单地确定哪个子树包含coverp q。这需要O(n),就像cover一样,我们只是保留更多信息。现在,以平行方式遍历这些路径并停在它们发散的地方。这始终是O(n)

如果您有从每个节点到其父节点的指针,您甚至可以通过生成“自下而上”路径来改进这一点,为您提供O(log n)平衡树。

请注意,这是一个时空权衡,因为当您的代码占用O(1)空间时,此算法会为​​平衡树提供O(log n)空间,并且通常会占用O(n)空间。 / p>

答案 1 :(得分:0)

正如hammar’s answer所示,随着许多操作的重复,您的算法效率非常低。

我会采用不同的方法:如果两个给定节点不在同一个子树中(从而使它成为第一个共同的祖先),而不是测试每个潜在的根节点,我将确定从根到两个给定节点并比较节点。从根向下的路径上的最后一个公共节点也是第一个共同的祖先。

这是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();
}

pathToNodecommonAncestor都在O(n)中。