0

I was asked this question in an interview. Given are two BST (Binary Search Tree). We need to traverse the two trees in such a way that a merged sorted output is the result. Constraint is that we cannot use extra memory like arrays. I suggested a combined inorder traversal of both the trees. The approach was correct but I got stuck in recursion and was not able to write the code.

Note: We cant merge the two trees into one.

Please someone guide me in this direction. Thanks in advance.

Cratylus
  • 52,998
  • 69
  • 209
  • 339
user1225752
  • 77
  • 1
  • 8

3 Answers3

0

The "simplest" way would be to:

  1. Convert tree A to a doubly linked list (sorted)
  2. Convert tree B to a doubly linked list (sorted)
  3. Traverse the sorted lists printing minimum (easy)
  4. Convert list A to tree A
  5. Convert list B to tree B

You can find algorithms for this steps online.
I don't think doing a parallel traversal of trees is possible. You would need additional information e.g. a visited flag to eliminate left subtree as visited and even then you would run into other problems.
If anyone knows how this would be possible with a parallel traversal I would be happy to know it.

Cratylus
  • 52,998
  • 69
  • 209
  • 339
  • I've upd'd my answer with the solution. Whaddya think? – Will Ness Feb 24 '13 at 14:31
  • BTW whether your solution is acceptable or *not*, is predicated on the question about `Leaf` representation in this BST: Is leaves are represented by `Nodes` with two NULL child node pointers, then it's OK and you can reuse them for the DL links. But if leaves are represented by a special linkless `Leaf` structure, then you will have to allocate new memory for the links in doubly linked list for the leaves. – Will Ness Feb 24 '13 at 14:48
  • @WillNess:I am not sure what is the algorithm for the parallel traversal.From my point of view doing parallel traversal of both trees will not work as you can not know whether you have already visited a subtree or not – Cratylus Feb 25 '13 at 09:46
  • I've clarified my answer. Please respond there. :) Thank you. Here I made another claim. – Will Ness Feb 25 '13 at 10:36
  • to your question: each traversal is done separately, one does not know about the other. The results are interleaved. For that to be possible on non-parallel hardware, each must be stoppable - and restartable from the point where it was previously stopped. This is the essence of generators concept, achieved with `yield` statement in Python. Or with continuations, in Scheme. Or via lazy semantics of Haskell. – Will Ness Feb 25 '13 at 10:40
  • @WillNess:How would that be implemented in Java?Is it possible? – Cratylus Feb 25 '13 at 10:43
  • and BTW this problem is very similar to the famous ["same fringe problem"](http://c2.com/cgi/wiki?SameFringeProblem). I linked the 1st result from G-search, and it speaks of "coroutines". So [if Java supports coroutines](http://en.wikipedia.org/wiki/Coroutine#Implementations_for_Java), this is how it could be done in Java then. My algo here seems to be O(n*log(m)) in time and doesn't need coroutines, because of that. The real solution is O(n+m) time and O(1) (explicit) space (implicit space will be O(log(nm)) of course - for not too *unbalanced* trees). – Will Ness Feb 25 '13 at 11:19
  • ... or, in Java, with some other form of concurrency. We just need restartable processes. So if you can create two threads, of which each will (produce one fringe elt and then go to sleep) on command, you could then do this. (reading the c2 wiki page, your solutions seems to be similar to one by John MacCarthy himself.) – Will Ness Feb 25 '13 at 11:25
0

I am assuming that there are no links to parent or next nodes in the tree, because otherwise this would be quite easy: Your just iterate your trees by following these links and write your merge algorithm as you would for linked lists.

If you don't have next or parent links, you cannot write simple recursion. You'll need two "recursion" stacks. You can implement the following structure, which allows you to iterate the each of the trees separately.

class Iterator
{
    stack<Node> st;
    int item(){
       return st.top().item();
    }
    void advance(){
        if (st.top().right != null)
            st.push(st.top().right);
            // Go as far left as possible
            while (st.top().left != null) st.push(st.top().left);
        else {
            int x = st.top().item();
            // we pop until we see a node with a higher value
            while(st.top().item()<=x) st.pop(); 

        }
    }
};

Then write your merge algorithm using two of these iterators.

You will need O(log n) space, but asymptotically this isn't more than any recursive iteration.

RareBox
  • 141
  • 4
  • The OP specifically asks for a solution with no extra space. – Cratylus Feb 24 '13 at 11:17
  • Any recursion of the tree uses O(log n) space. There is simply no possible solution that uses constant extra space. Logarithmic space for linear time operation is not a big deal. – RareBox Feb 24 '13 at 12:17
0
print $ merge (inorder treeA) (inorder treeB)

what's the problem?

(notice, the above is actual Haskell code which actually runs and performs the task). inorder is trivial to implement with recursion. merge is a nearly-standard feature, merging its two argument ordered (non-decreasing) lists, producing an ordered output list, keeping the duplicates.

Because of lazy evaluation and garbage collection, the lists are not actually created - at most one produced element is retained for each tree, and is discarded when the next one is produced, in effect creating iterators for the traversals (each with its own internal state).


Here's the solution (if your language does not support the above, or the equivalent yield mechanism, or the explicit continuations of Scheme which allow to switch between two contexts deep inside control stack each (thus making it possible to have "two recursions" in parallel, as in the above)):

They don't say anything about time complexity, so we can do a recursive traversal of 1st tree, and traverse the 2nd tree anew, for each node of the 1st tree - while saving previous value on 1st. So, we have two consecutive values on 1st tree, and print all values from 2nd tree between them, with fresh recursive traversal, restarting from the top of the 2nd tree for each new pair of values from the 1st tree.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • Very interesting for me who don't know Haskell.But in the context of an interview question, I am not sure this is what the interview expected. – Cratylus Feb 24 '13 at 11:17
  • @Cratylus if you allowed to use Python this basically means just use `yield` instead of `return` inside your recursive traversal functions, that's all. – Will Ness Feb 24 '13 at 11:21
  • May be you are right.I don't know scripting languages so perhaps you could get away with it for a Python position. – Cratylus Feb 24 '13 at 11:27
  • @Cratylus there is no specific language tag on this question, right? If you don't have `yield` in your language, you will have to do some reification yourself (i.e. convert implicit call stack of recursion into explicit stack of pointers into the tree as you traverse it, like the other answer shows). Or use standard iterators, if in C++. – Will Ness Feb 24 '13 at 11:31
  • Then you go back to my original comment.To implement what you written in a one line of code yourself. – Cratylus Feb 24 '13 at 11:44
  • @Cratylus some interviews expect code, and some expect higher-level algorithm descriptions. – Will Ness Feb 24 '13 at 11:51
  • IMHO this is a hard question. – Cratylus Feb 24 '13 at 12:09