Showing posts with label traversal. Show all posts
Showing posts with label traversal. Show all posts

Saturday, 4 May 2013

Mirror a binary tree

 

Introduction

  Mirroring a binary tree, a common question nowadays to understand whether the candidate knows about tree traversal. Asking plainly in-order/pre-order is of no use!! :)

 

Solution 

The dummies way is to directly jump to code which never works even for such a simple problem.

First thought, i got this code:

Tree* mirror(Tree* node)
{
    if(node == NULL)
        return NULL;
 
    node->left = mirror(node->right);
    node->right = mirror(node->left);
 
    return node;
} 

Flaws in it?

- This is a mugged up code!!

- You didn’t think about whether to output the mirrored tree in a new data structure or to do it in-place

- Why you cannot use tail recursion for in-place,

- Reason 1: you need to swap left & right using a temporary storage. You need to expand the tree on the stack

- Reason 2: The above kind of swapping loses the node->left data whatever magic your recursion does.

 

Now, we got full of issues, think basic, non-iterative way

treemirror

tmp = tree->left;
tree->left = tree->right;
tree->right = tmp;

Whatever way you follow, you need to do the above swap.

Corrected code for reproduction of mirrored tree, as a copy

Tree* mirror(Tree* node)
{
    if(node == NULL)
        return NULL;
 
    Tree *newNode = createNode(node->data);
    newNode->left = mirror(node->right);
    newNode->right = mirror(node->left);
 
    return newNode;
}

Correct code for creating mirrored tree, in-place

- do a post order traversal to read the full tree into the recursion stack

- do the primitive swap operation

Tree* mirror(Tree* node)
{
    if(node == NULL)
        return NULL;
 
    mirror(node->left);
    mirror(node->right);
    Tree *tmp = node->left;
    node->left = node->right;
    node->right = tmp;
 
    return node;
}

Thursday, 15 March 2012

Searching BST

“Scanning through an English dictionary is faster because, you directly go to the indexed character and scan through letter one-by-one to get to the exact text you are searching for”

Preface

  Sorry for not updating for a long period of around 5 months. Really struck up with busy schedules. Now I forgot all the data structures and algorithms. So need to relearn and hence will revisit all my previous posts before going into medium difficulty content.

References

http://analgorithmaday.blogspot.in/2011/03/successor-and-predecessorbst.html

Introduction

  How would you design a search algorithm which does a search through the English dictionary in really fast manner?  Manually, you can easily check a dictionary based on lexicographic ordering. But how would you design a data structure which can naturally allow you to do that? you can do this with a binary search tree.

In previous article, we learnt about Hash Tables(http://analgorithmaday.blogspot.in/2011/10/hash-tables-dictionaries.html) that it is an array index to data mapping. This is a direct mapping hash table which can be generically implemented using binary search trees. If you check the implementations of map and hash_map in C++ STLs, these are implemented using a kind of binary search trees.

Concept

   We decide left or right of the binary search tree based on the value we need to search for. It reduces loops logarithmically.

  Bonus: We can also check how to get the 4th smallest element out of the binary search tree.  This can be done by having an extra field which can calculate the rank of the node.

Rank of a node in BST is number of nodes in the left sub-tree + 1. This is nothing but number of keys less than the current value. This rank is helpful to identify the successor and predecessor easily.

If space is not a constraint, then we should go with ranking the nodes. But if its a constraint, then you should identify the successor of each node

Code

Searching a binary search tree (iteration)

class BST {
public:
    int data;
    BST* parent;
    BST* left;
    BST* right;
};
 
 
BST* createBSTNode(int data)
{
    BST* newNode = new BST();
    newNode->data = data;
    newNode->left = NULL;
    newNode->right = NULL;
    newNode->parent = NULL;
    return newNode;
}
 
void bst_insert(BST*& tree, BST* node)
{
    BST* prev = NULL;
    BST* iter=tree;
    while(iter != NULL)
    {
        prev = iter;
        if(node->data > iter->data)
            iter = iter->left;
        else
            iter = iter->right;
    }
    // found node is parent to our node
    node->parent = prev;
 
    // if prev is NULL
    // then this is the first node
    // change the root NODE
    if(prev == NULL)
        tree = node;
    else {
        // now we need to decide where the node
        // should go: left or right
        if(node->data > prev->data)
            prev->left = node;
        else
            prev->right = node;
    }
 
}
 
BST* convert_arr_bst(int* arr, int size)
{
    BST* tree=NULL;
    for(int i=0;i<size;i++) {
        bst_insert(tree, createBSTNode(arr[i]));
    }
    return tree;
}
 
void main()
{
    BST *tree, *tmp;
    int k = 6;
    bool found = false;
    int arr[]= {2,30,12,10,22,6};
    tree = convert_arr_bst(arr, 6);
    tmp = tree;
    while(tmp && !found)
    {
        if(tmp->data == k) found = true;
        if(tmp->data < k) tmp = tmp->left;
        else {
            tmp = tmp->right;
        }
    }
    if(found == true)
        cout<<"Found"<<endl;
    else
        cout<<"Not Found"<<endl;
}

Searching a binary search tree based on ranks

Will see this in next article about constructing binary search trees with ranks.

Wednesday, 15 June 2011

Construct Single threaded binary tree–In-order successor connection

“The main advantage of threaded trees is the reduction in traversal time and space.. Tree traversal is really time consuming + stack space consuming. We can reduce that, if we have threaded the tree with the empty nodes at the bottom.”

Introduction

   Before diving into the concept behind this, understand and re-study the following links,

http://analgorithmaday.blogspot.com/2011/03/successor-and-predecessorbst.html

http://analgorithmaday.blogspot.com/2011/06/applications-of-traversals.html

In this article, we will just see how to thread the tree so that, all the leaf nodes get connected to its in-order successor nodes. As its a successor node, we connect the right nodes to the appropriate root.

Concept

The important property of the in-order successor node is that,

  • If the node you see has a NULL right node and the node is present as left child for its parent, simply connect the NULL node to the parent.
  • If the node you see has a NULL right node but its a right child for its parent, then move to a parent for which our node is not the right!!

This is already discussed clearly in successor and predecessor. But how to do this with recursion and without a parent node part of the tree!!

Little bit tricky!!.. :) But if you understand that, you need to create cycles with the current parent. you will catch the way!!

i.e,

Consider you have a tree like this,

            17062011384

Firstly, you need to do recursion so that child gets connected to parent. That is,

7->3, 8->3, 3->1,9->4, 4->1.. etc.

After you get this recursion, connect 7->right to 3 and 8->right also to 3. But when you need to connect 3 to 1, you just check whether traversing 3->right->right gives again the 3.. :)

That is, 

          1

     3

7       8 –> right node connected to 3.

Between 3 and 8 there is a cycle, just detect this cycle and move the 8-th right node to the parent of 3. which is 1. :)

The above is what we need to achieve to get the in-order successor.

Code

 
Tree* makeThreaded(Tree* root)
{
    if(root == NULL)
        return NULL;
 
    Tree* leftPrev = makeThreaded(root->left);
    if(leftPrev != NULL) {
        std::cout<<leftPrev->data;
        std::cout<<":"<<root->data<<std::endl;
        Tree* iter = leftPrev;
        while(iter->right != NULL && iter->right != leftPrev)
            iter = iter->right;
 
        iter->right = root;
    }
    Tree* rightPrev = makeThreaded(root->right);
    if(rightPrev != NULL) {
        std::cout<<rightPrev->data;
        std::cout<<":"<<root->data<<std::endl;
        Tree* iter = rightPrev;
        while(iter->right != NULL && iter->right != rightPrev)
            iter = iter->right;
 
        iter->right = root;
    }
 
    return root;
}
 
Tree* tobj = newTree();
Tree* root = makeThreaded(tobj); // there will be no change in root
  • we need to iterate until we find the right!!.. That’s why we need that for loop.
  • we don’t use any extra space in this algorithm. We reuse the NULL nodes in the tree.
  • We don’t have any difference between threaded node and normal node. :) We can traverse as usual.

Tuesday, 14 June 2011

Counting number of nodes in a binary tree

 

Introduction

   How would you count the number of nodes in a tree? Traverse all the nodes in a tree. You can do any traversal and just start counting. But there is a concept called tail recursion which is handy and very easily readable.

If you have a tree, if you know the number of nodes in the left and number of nodes in right +1, then, what you get is total number of nodes.

Concept

   You have to do tail recursion with count(root->left) + count(root->right)+1.

After you get the count, how you will find the height of the tree ?

  log2 count_of_nodes. But in C++, you won’t get a function to calculate log base 2. :)

There is a way,

  log10 n/log 10 2 = log 2 n

So, you just need to use the above formula to get log base 2 which yields the height of the tree.

Code

Tree* tobj;
 
int count(Tree* root) {
    if(root == NULL)
        return 0;
    else 
        if(root->left == NULL && root->right == NULL)
            return 1;
        else
            return count(root->left) + count(root->right) + 1;    
}
 
cout<<count(tobj);
  • you are covering 2 base conditions here, root == NULL then 0. root->left & root->right is NULL, then only root node is there, which is 1.
  • This expands to 1 for each node in the left & right.

Monday, 13 June 2011

Level order insert in a binary tree

“We have seen binary search trees, heaps.. how would you normally create a dynamic tree from a list. Simply, you are given an array, you need to convert it into a binary tree in level order.”

Introduction

    Level order traversal needs a level-by-level movement in a tree. But if you want to recreate the tree from an array in a level-order way. It should be easy. Just you need to fill the elements with the correct formula as we already discussed.

       2i             -----> root

2i+1     2i+2    --> left      right

This is the basis for the level order insert.

Concept

   If you check the level order traversal, many uses stacks. But we should avoid using stacks. Instead, we will use recursion for our case.

  Understanding recursion requires a simple base condition which needs to be repeated for different argument.

     for example, if you have an array,

     arr = {0,1,2,3,4,5,6,7,8,9}

the tree becomes like this,

                  0

           1                    2

    3           4           5      6

7     8     9

For 0, we need to create 1 and 2. For 1, 3, 4. For 3, create 7,8. Similarly for 4, create 9.

This means, you need to do recursion left with left index and right with right index. The end condition is when left or right index is exceeding the size of the array.

Code

Tree *tobj;
 
void level_order_insert(Tree* root, int* arr, int start, int size)
{
    int left = 2*start+1;
    int right = 2*start+2;
 
    if(left > size || right > size)
        return;
 
    if(root == NULL) {
        tobj = createNode(arr[start]);
        root = tobj;
    }
 
    if(root->left == NULL && root->right == NULL) {
        if(left < size)
            root->left = createNode(arr[left]);
        if(right < size)
            root->right = createNode(arr[right]);
    }
    level_order_insert(root->left, arr, left, size);
    level_order_insert(root->right, arr, right, size);
 
}
 
main() {
   int A[] = {0,1,2,3,4,5,6,7,8,9}
   level_order_insert(tobj, A, 0, 10);
}
  • Before root->left = createNode. You need to make sure that left is still < size. This is to handle the border case
  • root->left gets start as all left with all odd numbers
  • root->right gets start as all right with all even numbers.
  • Most of the times you get partially balanced tree with this method.