My problem is as follows:
I have a binary search tree with keys: a1<a2<...<an
, the problem is to print all the (a_i, a_i+1) pairs in the tree (where i={1,2,3,...}) using a recursive algorithm in O(n) time without any global variable and using O(1) extra space. An example:
Let the keys be: 1,2, ..., 5
Pairs that will be printed: (1,2) (2,3) (3, 4) (4, 5)
So you can't do inorder traversal in the tree and find the successor/predecessor for each node. Because that would take O(nh) time and if the tree is balanced, it will be O(nlgn) for the whole tree.
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:
- Find the lowest value in the tree by starting at the root and repeatedly following the left child pointer until no such pointer exists.
- Until all nodes are visited, remember the current node and find its successor as follows:
- If the current node has a right child:
- Go right.
- Go left until there are no left children left.
- Output the node you started at, then this node.
2: Otherwise:
- Walk up to the parent node until you find that the node you started at was a left child of its parent.
- If you hit the root and never found that you traversed from a left child upward, you are done.
- 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;
}
}