以下是我找到第一个共同祖先的算法。但我不知道如何计算时间复杂度,有人可以帮忙吗?
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);
}
答案 0 :(得分:9)
好的,让我们首先确定这种算法的最坏情况。 covers
从左到右搜索树,因此如果您要搜索的节点是最右边的叶子,或者根本不在子树中,则会出现最坏情况。此时您将访问子树中的所有节点,因此covers
是 O(n),其中 n 是树中的节点数
同样,当commonAncestor
和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)
。
但你可以做得更好。
例如,让我们确定p
和q
的完整路径,而不是简单地确定哪个子树包含cover
或p
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();
}
pathToNode
和commonAncestor
都在O(n)中。