50

This is not homework, this is an interview question.

The catch here is that the algorithm should be constant space. I'm pretty clueless on how to do this without a stack, I'd post what I've written using a stack, but it's not relevant anyway.

Here's what I've tried: I attempted to do a pre-order traversal and I got to the left-most node, but I'm stuck there. I don't know how to "recurse" back up without a stack/parent pointer.

Any help would be appreciated.

(I'm tagging it as Java since that's what I'm comfortable using, but it's pretty language agnostic as is apparent.)

user183037
  • 2,549
  • 4
  • 31
  • 42

10 Answers10

30

How about Morris Inorder tree traversal? Its based on the notion of threaded trees and it modifies the tree, but reverts it back when its done.

Linkie: http://geeksforgeeks.org/?p=6358

Doesn't use any extra space.

brainydexter
  • 19,826
  • 28
  • 77
  • 115
  • Thank you - so **this** was what was being asked for. Thanks again! – user183037 Mar 31 '11 at 21:41
  • 2
    If it helps you any bit, I asked a question about how the tree is restored here: http://stackoverflow.com/questions/5502916/please-explain-morris-inorder-tree-traversal-without-using-stacks-or-recursion – brainydexter Mar 31 '11 at 21:49
  • @user183037 Interesting concept, but this does not use a Binary Search Tree, so it isn't quite inline with the OP's question. Binary Search Tree being left values are always less than and right values are always greater than the value of the parent node. – Levitikon Jan 25 '13 at 13:28
  • 2
    @brainydexter Good link but the Morris traversal explained runs in O(nlogn) time complexity and not O(n) time as asked. – Nikunj Banka Mar 21 '14 at 07:32
  • 1
    with just a link, this'll become useless when the link dies – ivan_pozdeev Sep 13 '16 at 04:48
  • @NikunjBanka The Morris traversal is O(n) not O(nlogn). The linked article explains why. But basically, when we are looking for preorder node (by going down the right child nodes of the left child) those nodes are visited for this purpose only once. And then of course once more when they are processed themselves. – Incassator Aug 04 '22 at 01:41
29

I didn't think it through entirely, but i think it's possible as long as you're willing to mess up your tree in the process.

Every Node has 2 pointers, so it could be used to represent a doubly-linked list. Suppose you advance from Root to Root.Left=Current. Now Root.Left pointer is useless, so assign it to be Current.Right and proceed to Current.Left. By the time you reach leftmost child, you'll have a linked list with trees hanging off of some nodes. Now iterate over that, repeating the process for every tree you encounter as you go

EDIT: thought it through. Here's the algorithm that prints in-order:

void traverse (Node root) {
  traverse (root.left, root);
}

void traverse (Node current, Node parent) {
  while (current != null) {
    if (parent != null) {
      parent.left = current.right;
      current.right = parent;
    }

    if (current.left != null) {
      parent = current;
      current = current.left;
    } else {
      print(current);
      current = current.right;
      parent = null;
    }
  }
}
sigjuice
  • 28,661
  • 12
  • 68
  • 93
iluxa
  • 6,941
  • 18
  • 36
  • 2
    Oh yes, that is a good idea. It's called pointer reversal and sometimes used by garbage collectors. It has relation to Huet's zipper. But as you said, it messes up your tree and often violates static type safety. – jmg Mar 31 '11 at 07:41
  • 1
    (+1) for the idea, but please explain why you think this has `O(n)` time complexity. – NPE Mar 31 '11 at 09:56
  • Because every node gets visited exactly once (one iteration, one node), and there's a constant amount of operations in an iteration – iluxa Mar 31 '11 at 10:00
  • 2
    What happens if the root just has a right subtree .. it will never enter the while loop and the tree would not be traversed ! ... while(current != null|| parent!= null) should fix it imo .. – codeObserver Jul 13 '11 at 05:35
  • When one just needs to traverse the tree from the root to a given node, and back again (like when inserting a node in an AVL tree and performing rotations on the way back to the root), one can still use this pointer reversal technique. Go down left (resp. right) branch: `next = current.left /*resp. right*/; current.left /*resp. right*/ = parent; parent = current; current = next;`. When going up: `if (current.value < parent.value) { grandparent = parent.left; parent.left = current; } else { grandparent = parent.right; parent.right = current; } current = parent; parent = grandparent;`. – Suzanne Soy Oct 30 '12 at 18:14
5

If you are using a downwards pointer based tree and don't have a parent pointer or some other memory it is impossible to traverse in constant space.

It is possible if your binary tree is in an array instead of a pointer-based object structure. But then you can access any node directly. Which is a kind of cheating ;-)

jmg
  • 7,308
  • 1
  • 18
  • 22
  • 1
    Well, I wouldn't call that cheating, but you're forgetting that the algorithm needs to run on constant space - introducing an array is going to take O(N) space :) – user183037 Mar 31 '11 at 07:30
  • @user183037: No, I meant if the tree is genuinely stored in an array instead of a pointer based structure. Such a representation is unusual but sometimes very efficient and useful. I'll edit my answer to make that clear. – jmg Mar 31 '11 at 07:32
  • I think jmg means that the original tree you want to traverse is already stored in an array. – ChrisWue Mar 31 '11 at 07:34
  • @ChrisWue: Exactly, that is what I meant. – jmg Mar 31 '11 at 07:35
  • and @ChrisWue - sorry, my bad. But I doubt that it would have been an array implementation. Good catch though! – user183037 Mar 31 '11 at 07:39
  • 1
    "If you are using a downwards pointer based tree and don't have a parent pointer or some other memory it is impossible to traverse in constant space." - This isn't true if you are allowed to modify the tree during traversal (and then undo the changes). See http://www.geeksforgeeks.org/inorder-tree-traversal-without-recursion-and-without-stack/ – mdenton8 May 14 '16 at 20:21
  • @mdenton8: You are absolutely right, then you could do the usual pointer reversal, aka zipper trick. – jmg Jul 07 '16 at 06:29
3

Here's a shorter version iluxa's original answer. It runs exactly the same node manipulation and printing steps, in exactly the same order — but in a simplified manner [1]:

void traverse (Node n) {
  while (n) {
    Node next = n.left;
    if (next) {
      n.left = next.right;
      next.right = n;
      n = next;
    } else {
      print(n);
      n = n.right;
    }
  }
}

[1] Plus, it even works when the tree root node has no left child.

Community
  • 1
  • 1
ens
  • 1,068
  • 13
  • 14
0

Accepted answer needs the following change otherwise it will not print the tree where the BST only has one node

if (current == NULL && root != NULL)
   print(root);
Ondrej Janacek
  • 12,486
  • 14
  • 59
  • 93
Steffen San
  • 13
  • 1
  • 4
0

minor special case for iluxa's answer above

if(current== null)
        {
            current = root;
            parent = current.Right;
            if(parent != null)
            {
                current.Right = parent.Left;
                parent.Left = current;
            }
        }
0

It's a a search tree, so you can always get the next key/entry

You need smth like (I didn't test the code, but it's as simple as it gets)

java.util.NavigableMap<K, V> map=...
for (Entry<K, V> e = map.firstEntry(); e!=null; e = map.higherEntry(e.getKey())) {
  process(e)
}

for clarity this is higherEntry, so it's not recursive. There you have it :)

final Entry<K,V> getHigherEntry(K key) {
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = compare(key, p.key);
        if (cmp < 0) {
            if (p.left != null)
                p = p.left;
            else
                return p;
        } else {
            if (p.right != null) {
                p = p.right;
            } else {
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                while (parent != null && ch == parent.right) {
                    ch = parent;
                    parent = parent.parent;
                }
                return parent;
            }
        }
    }
    return null;
}
bestsss
  • 11,796
  • 3
  • 53
  • 63
  • 3
    Do you the line `Entry parent = p.parent;`. This is the line where this code uses a parent pointer. Which is usually not present in a binary search tree. Otherwise, the interview question would be trivial. – jmg Mar 31 '11 at 07:45
  • 1
    And, this is not O(N) either AFAIK. – user183037 Mar 31 '11 at 07:46
  • it's N*logN, but you can keep the pointer to the node to remove the logN. – bestsss Mar 31 '11 at 07:50
  • @jmg, no one said there should be no parent. Red/Black is definitely a binary search tree. – bestsss Mar 31 '11 at 07:54
  • @bestsss: Yes, but this implementation is more than just a binary search tree. – jmg Mar 31 '11 at 07:55
  • @bestsss - it's in the question description and in one of the comments on the question actually. Having a parent pointer makes the solution very straight-forward. – user183037 Mar 31 '11 at 07:58
  • @jmg, it's an interview question and as far as I saw, there was no reference how to impl. the tree. – bestsss Mar 31 '11 at 07:59
0

The title of the question doesn't mention the lack of a "parent" pointer in the node. Although it isn't necessarily required for BST, many binary tree implementations do have a parent pointer. class Node { Node* left; Node* right; Node* parent; DATA data; };

It this is the case, imaging a diagram of the tree on paper, and draw with a pencil around the tree, going up and down, from both sides of the edges (when going down, you'll be left of the edge, and when going up, you'll be on the right side). Basically, there are 4 states:

  1. SouthWest: You are on the left side of the edge, going from the parent its left child
  2. NorthEast: Going from a left child, back to its parent
  3. SouthEast: Going from a parent to a right child
  4. NorthWest: Going from a right child, back to its parent
Traverse( Node* node )
{
    enum DIRECTION {SW, NE, SE, NW};
    DIRECTION direction=SW;

    while( node )
    {
        // first, output the node data, if I'm on my way down:
        if( direction==SE or direction==SW ) {
            out_stream << node->data;
        }

        switch( direction ) {
        case SW:                
            if( node->left ) {
                // if we have a left child, keep going down left
                node = node->left;
            }
            else if( node->right ) {
                // we don't have a left child, go right
                node = node->right;
                DIRECTION = SE;
            }
            else {
                // no children, go up.
                DIRECTION = NE;
            }
            break;
        case SE:
            if( node->left ) {
                DIRECTION = SW;
                node = node->left;
            }
            else if( node->right ) {
                node = node->right;
            }
            else {
                DIRECTION = NW;
            }
            break;
        case NE:
            if( node->right ) {
                // take a u-turn back to the right node
                node = node->right;
                DIRECTION = SE;
            }
            else {
                node = node->parent;
            }
            break;
        case NW:
            node = node->parent;
            break;
        }
    }
}
Uri London
  • 10,631
  • 5
  • 51
  • 81
  • Please read the question description, there's a point to a description field you know :p There is no parent pointer, if there was, the answer is trivial. – user183037 Mar 31 '11 at 15:25
0

We can traverse the binary tree without modifying the tree itself (provided nodes have parent pointer). And it can be done in constant space. I found this useful link for the same http://tech.technoflirt.com/2011/03/04/non-recursive-tree-traversal-in-on-using-constant-space/

Priyanka
  • 101
  • 1
  • 5
-1

It's a binary search tree, so every node can be reached by a series of right/left decision. Describe that series as 0/1, least-significant bit to most-significant. So the function f(0) means "the node found by taking the right-hand branch until you find a leaf; f(1) means take one left and the rest right; f(2) -- that is, binary 010 -- means take a right, then a left, then rights until you find a leaf. Iterate f(n) starting at n=0 until you have hit every leaf. Not efficient (since you have to start at the top of the tree each time) but constant memory and linear time.

Michael Lorton
  • 43,060
  • 26
  • 103
  • 144
  • 5
    You need `log(n)` bits to do this, so I doubt this qualifies as "constant space". – NPE Mar 31 '11 at 07:28
  • But then you have to store a series of bits (0/1 values) which can't be done in constant space unless you know an upper bound on the depth of the tree. But then even recursion uses only constant space. – jmg Mar 31 '11 at 07:30
  • 1
    this isn't linear time, it's nlogn if the tree is balanced and worse if it is not. – Bwmat Mar 31 '11 at 07:32
  • @jmg - are you sure? Recursion uses a stack internally, doesn't it? – user183037 Mar 31 '11 at 07:33
  • @aix -- That's true. If you had, for example, 18,446,744,073,709,551,616, you would need all of a 64-bit number to hold it. I'm not worried. (Yeah, a grossly imbalanced tree could defeat my algorithm, but it would defeat *any* constant-space algorithm, so I think it's fair to exclude it.) – Michael Lorton Mar 31 '11 at 07:34
  • @Bwmat -- it's n * m where m is the depth, but the depth is realistically limited (and yes, bounded by log(n)). NB: I'm assuming everybody is using n to mean the number of nodes. – Michael Lorton Mar 31 '11 at 07:37
  • @user183037: Yes, I'm sure. And yes recursion uses a stack, but if the depth of the tree has an upper bound, the recursion depth has a proportional upper bound. Therefore, you can determine beforehand how much memory you will need. And that is usually called "constant space". – jmg Mar 31 '11 at 07:38
  • @user183037: The concept of recursion has no inherent complexity. Depending on the context, a recursive function may be in any complexity class, e.g. O(1), O(log n), O(n), O(n^n) and so on. – jmg Mar 31 '11 at 07:47
  • 2
    @jmg - recursion is log(n) space. Anything that's in any way dependent on size of input (N) is f(N) and is not constant. If the tree isn't balanced, depth of recursion will equal N - how can you call that constant? – iluxa Mar 31 '11 at 07:52
  • @iluxa: I said "the concept of recursion" and meant in general, not a recursive function to iterate through a pointer based binary search tree without parent pointers. – jmg Mar 31 '11 at 07:57
  • 1
    @jmg ok then. It's just that 2 comments ago you suggested that when "you can determine beforehand how much memory you will need, it's constant space", and that's incorrect. – iluxa Mar 31 '11 at 08:04
  • 1
    it's constant space if you can determine beforehand an upper bound for the space needed, independent of n – Bwmat Mar 31 '11 at 08:06
  • @iluxa: Bwmat rephrased exactly what I said, I hope that make it clear to you. – jmg Mar 31 '11 at 09:03
  • @jmg and @Bwmat - so are you saying that, for example, to copy an array over to another array, you'll be using "constant space" since you already know how big the first array is? – user183037 Mar 31 '11 at 15:28
  • @user183037: Yes, if for every possible input of the code under investigation the array size is constant. If you're interested in that, try to find an introduction into complexity theory. – jmg Apr 01 '11 at 09:15