我的问题如下:
我有一个带键的二叉搜索树:a1<a2<...<an
,问题是打印树中的所有(a_i,a_i + 1)对(其中i = {1,2,3,... })在O(n)时间内使用递归算法,没有任何全局变量并使用O(1)额外空间。一个例子:
让键为:1,2,...,5
将被打印的对:(1,2)(2,3)(3,4)(4,5)
因此,您无法在树中进行顺序遍历并找到每个节点的后继/前导。因为这将花费O(nh)时间并且如果树是平衡的,则整个树将是O(nlgn)。
答案 0 :(得分:3)
虽然你是正确的,找到有序继任者或前任可能花费O(h)时间,但事实证明,如果你从BST中的最小值开始并反复找到它的后继者,那么完成的工作总量无论树的形状如何,最终都会出现在O(n)处。
直觉上这是考虑在迭代地进行后继查找时遍历树中每条边的次数。具体来说,您将访问树中的每个边缘两次:一次下降到子树时,一次走出它时访问了该子树中的每个节点。由于n节点树具有O(n)个边,因此需要时间O(n)才能完成。
如果您对此持怀疑态度,请尝试编写一个简单的程序来验证它。我记得在我第一次听到它之前不相信这个结果,直到我写了一个程序来确认它。 : - )
在伪代码中,逻辑看起来像这样:
希望这有帮助!
答案 1 :(得分:1)
Oli是正确的,inorder遍历是O(n),但你是正确的,使用一般的后继/前任例程会增加算法的复杂性。所以:
一个简单的解决方案是使用有序遍历来遍历树,跟踪上次使用右指向边缘(例如,使用名为 last_right_ancestor_seen 的变量指向到它的父节点)和你见过的最后一个叶子节点(例如,在 last_leaf_seen (实际上,任何没有右子节点的节点)。每次处理叶子节点时,它的前身是 last_right_ancestor ,每当你点击一个非叶节点时,它的前身是 last_leaf_seen ,你只打印两个.O(n)时间,O(1)空格。 / p>
希望它足够清楚,如果没有,我可以画一张图。
编辑:这是未经测试但可能是正确的:
walk(node* current, node* last_right_ancestor_seen, node* last_leaf_seen) {
if(current->left != null) {
walk(current->left, last_right_ancestor_seen, last_leaf_seen);
}
if(current->is_leaf()) {
if(last_right_ancestor_seen != null)
print(last_right_ancestor_seen->value, current->value);
}
else {
print(last_leaf_seen->value, current->value);
}
if(current->right != null) {
*last_right_ancestor_seen = *current;
walk(current->right, last_right_ancestor_seen, last_leaf_seen);
}
else {
*last_leaf_seen = *current;
}
}