Printing Successor and Predecessor in a BST

[亡魂溺海] 提交于 2019-12-06 17:30:30

Although you are right that finding an in-order successor or predecessor might take O(h) time, it turns out that if you start at the smallest value in a BST and repeatedly find its successor, the total amount of work done ends up coming out to O(n) regardless of the shape of the tree.

On intuition for this is to think about how many times you traverse each edge in the tree when iteratively doing successor lookups. Specifically, you will visit every edge in the tree exactly twice: once when you descend into the subtree, and once when you walk up out of it having visited every node in that subtree. Since an n-node tree has O(n) edges, this takes time O(n) to complete.

If you're skeptical about this, try writing a simple program to verify it. I remember not believing this result when I first heard it until I wrote a program to confirm it. :-)

In pseudocode, the logic would look like this:

  1. Find the lowest value in the tree by starting at the root and repeatedly following the left child pointer until no such pointer exists.
  2. Until all nodes are visited, remember the current node and find its successor as follows:
    1. If the current node has a right child:
      1. Go right.
      2. Go left until there are no left children left.
      3. Output the node you started at, then this node. 2: Otherwise:
      4. Walk up to the parent node until you find that the node you started at was a left child of its parent.
      5. If you hit the root and never found that you traversed from a left child upward, you are done.
      6. Otherwise, output the node you remembers, then the current node.

Hope this helps!

Oli is right that inorder traversal is O(n), but you are right that using the general successor/predecessor routines will increase the algorithmic complexity. So:

A simple solution would be to walk the tree using in-order traversal, keeping track of the last time you're taken a right-pointing edge (say, using a variable called last_right_ancestor_seen to point to its parent node) and the last leaf node you've seen (say, in last_leaf_seen (actually, any node without a right child). Every time you process a leaf node, its predecessor is last_right_ancestor, and every time you hit a non-leaf node, its predecessor is last_leaf_seen, and you just print the two. O(n) time, O(1) space.

Hope it's clear enough, I can draw you a diagram if not.

Edit: This is untested but probably correct:

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

}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!