在BST中打印后继者和前任者

时间:2013-01-06 13:57:43

标签: algorithm data-structures binary-tree traversal binary-search-tree

我的问题如下:

我有一个带键的二叉搜索树: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)。

2 个答案:

答案 0 :(得分:3)

虽然你是正确的,找到有序继任者或前任可能花费O(h)时间,但事实证明,如果你从BST中的最小值开始并反复找到它的后继者,那么完成的工作总量无论树的形状如何,最终都会出现在O(n)处。

直觉上这是考虑在迭代地进行后继查找时遍历树中每条边的次数。具体来说,您将访问树中的每个边缘两次:一次下降到子树时,一次走出它时访问了该子树中的每个节点。由于n节点树具有O(n)个边,因此需要时间O(n)才能完成。

如果您对此持怀疑态度,请尝试编写一个简单的程序来验证它。我记得在我第一次听到它之前不相信这个结果,直到我写了一个程序来确认它。 : - )

在伪代码中,逻辑看起来像这样:

  1. 通过从根开始并重复跟随左子指针直到没有这样的指针存在,找到树中的最低值。
  2. 在访问所有节点之前,请记住当前节点并找到其后续节点,如下所示:
    1. 如果当前节点有正确的子节点:
      1. 向右走。
      2. 向左走,直到没有剩下的孩子离开。
      3. 输出您开始的节点,然后输出此节点。 2:否则:
      4. 走到父节点,直到您发现您开始的节点是其父节点的左子节点。
      5. 如果你碰到根并且从未发现你从左边的孩子向上穿过,你就完成了。
      6. 否则,输出您记得的节点,然后输出当前节点。
  3. 希望这有帮助!

答案 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;
    }

}