5

I have been wondering what would be the space complexity for an iterative preorder traversal(using stack) for a Binary tree. I have referenced Elements of Programming Interviews and they stated that

The space complexity is O(h), where h is the height of the tree, since, with the exception of the top of the stack, the nodes in the stack correspond to the right children of the nodes on the path beginning at the root.

Following is the code for reference :

struct Node{
   int data;
   struct Node* left, right;
}
void printPreOrder(struct Node* root){
  if(!root)
   return ;
  stack<struct Node* > s;
  s.push(root);
  while(!s.empty()){
     struct Node *root_element = s.top();
     cout<<root_element->data<<" ";
     s.pop();
      if(root_element->right){
         s.push(root_element->right);
      }
      if(root_element->left){
         s.push(root_element->left);
      }
     }
     cout<<endl;
  }
  return ;
}

My intuition

While going through the algorithm I observed that the maximum number of entries in a stack at any instance can be max(num_of_leaves_in_left_subtree+1, num_of_trees_in_right_subtree). From this we can deduce that for a tree of height h, the maximum number of leaves can be 2^h. So, the maximum number of trees in left subtree would be 2^(h-1). Thus, the maximum number of entries in the stack would be 2^(h-1)+1. Thus, according to me, the space complexity for the above algorithm would be O(2^(log(n))).

Himanshu Kumar
  • 155
  • 1
  • 9

2 Answers2

9

First of all, your iterative implementation of preorder traversal has a mistake - you should push a right node and then a left one, but not vice versa.

Now the explanation - on each iteration you're going one level deeper and adding 2 elements (right and left if they exist) to the stack while popping one node out (the parent one). It means that at most 1 new element is added as you go 1 level down. Once you reach the left most node and pop it out you repeat the same procedure for the top node in the stack -> O(h).

For instance,

      1
     / \
   2     5
  / \   / \
 3   4 6   7

Step 0: 1 is added to the stack -> O(1)

Step 1: 1 removed, 2 and 5 are added -> O(2)

Step 2: 2 removed, 3 and 4 are added -> O(3)

Step 3: 3 removed -> O(2)

Step 4: 4 removed -> O(1)

Step 5: 5 removed, 6 and 7 are added -> O(2)

Step 6: 6 removed -> O(1)

Step 7: 7 removed -> O(0)

As you could see the space complexity was always proportional to the height of the tree.

In the worst case scenario (if tree looks like a list), the space complexity is O(1) for your implementation (as pointed out by @Islam Muhammad) because at each iteration of the while loop, one item is removed from the stack and one item is added (there's only 1 child).

Anatolii
  • 14,139
  • 4
  • 35
  • 65
0

Let us find it out by sequentially going through the algorithm.

Try to observe that the maximum stack size required for the entire tree rooted at root node will be equal to the maximum size of the stack required for the left subtree added with 1.

But How?

If you observe closely you would find that when we process the root node, we would be adding the right node and the left node to the stack and then progress with the top of the stack, i.e the left node.

So, if recursively define the function for finding the maximum size of the stack required, it would be as following :

function maxStackSizeReq(struct Node* root){
 if(!root)
    return 0; 
 return maxStackSizeReq(node->left)+1;
}

Now the explanation - on each iteration you're going one level deeper and adding 2 elements (right and left if they exist) to the stack while popping one node out (the parent one). It means that at most 1 new element is added as you go 1 level down. Once you reach the left most node and pop it out you repeat the same procedure for the top node in the stack -> O(h).

For instance, Let us try to find out the maximum size of stack required for the following tree.

     1
     / \
   2     5
  / \   / 
 3   4 6   

Step 0: Call maxStackSizeReq(root_1) -> return maxStackSizeReq(root_2)+1 Here we mean that, the maximum size of stack required would be the size of stack required for left subtree +1.

  2  
 / \  
3   4

Step 2: Call maxStackSizeReq(root_2) - > return maxStackSizeReq(root_3)+1 Here we mean that, the maximum size of stack required would be the size of stack required for left subtree +1. 3

Step 3: Call maxStackSizeReq(root_3) - > return maxStackSizeReq(root_3->left which is NULL)+1 Here we mean that, the maximum size of stack required would be the size of stack required for left subtree i.e NULL + 1. So, this would return 0

So, Step 3 returns 1 -> Step 2 return 2 -> Step 1 return 3;

So, finally the function would return 3 as the maximum size of stack required for processing preorder traversal of the tree rooted at root node.

How is the time complexity O(h) ? Well, if you followed the above algorithm carefully, you would also observe that we are traversing only the left subtree of the tree in a depth wise fashion. So, the above algorithm would have a space complexity of O(h) as O(h) recursive calls are being made. So, finally the space complexity for the preorder iterative stack implementation would also be O(h).

Remember

Sometimes, you may hear that the space complexity for preorder or inorder iterative solution is O(n). But, keep in mind that O(h) would be a better answer, because the space complexity is O(n) only for skewed trees, which is pretty obvious as h becomes n

1
 \
  2
   \
    3
Himanshu Kumar
  • 155
  • 1
  • 9