Time complexity of finding k successors in BST
Asked Answered
B

3

5

Given a binary search tree (BST) of height h, it would take O(k+h) time to apply the BST InOrder Successor algorithm k times in succession, starting in any node, applying each next call on the node that was returned by the previous call.

Pseudo code:

get_kth_successor(node):
    for times = 1 to k:
        node = successor(node)
    return node

How can I prove this time complexity?

In particular, I am trying to establish a relation between k and the number of nodes visited, but can't find any pattern here.

Bluefarb answered 12/11, 2016 at 9:0 Comment(5)
You mean starting from any node and finding k successors or finding the successors of any k nodes?Menopause
You are making k successive calls for what ??? what is this Tree successor algorithm. Please give a clear explanationStickseed
1.) Starting from any node and finding k successors. 2.) Tree successor algorithm finds the successor of a given node in a BST. I am calling this procedure k times.Bluefarb
@GameOfChess, So you have k nodes and you want to find successor for all the k given nodes. Am i correct ?Stickseed
@GameOfChess, I have edited your question quite a bit, hoping it clarifies it more. Could you check this indeed reflects your question?Visitant
V
5

Take the following truths concerning a successor(s) traversal:

  1. You can traverse a branch not more than two times: downward and upward.

  2. Every double visit of a branch corresponds to finding at least one more successor: when you backtrack via a branch upwards, you will have visited at least one successor more than at the time you passed that same branch the first time, in the downward direction.

  3. The number of branches you will traverse only once cannot be more than 2h. This worst case happens when you start at a leaf in the bottom-left side of the tree and must go all the way up to the root (a successor) and then down again to a bottom-leaf to find the successor of the root. But if you need more successors after that, you will have to visit some of these branches again (in backtracking) before you can traverse other branches for the first time. So the total number of branches you traverse only once cannot increase above 2h.

So, to find k successors you will at the most traverse k branches twice (downward and upward, cf. point 2) and 2h branches once (point 3), which comes down to a worst case branch-traversal-count of 2k+2h which is O(h+k).

Visitant answered 12/11, 2016 at 9:52 Comment(0)
S
4

I am going to write the full implementation for the problem in order to make it easy to prove my arguments about the time being taken.

.

Assuming that each node of BST has the following structure:

typedef struct node{
    int vale;
    struct node* left;
    struct node* right;
}node;

The algorithm will have 2 steps:

a. Start from the root node of the Tree and find the starting node and all the ancestor of that node. Store all this in the stack passed:

//root -> root node of the tree.
//val  -> value of the node to find.
// s   -> stack to store all ancestor.
node* find_node(node* root, int val,std::stack<node*> &s)
{
    if(root != NULL)  s.push(root);
    if(root == NULL || root->value == val) return root;
    if(root->value > val) return find_node(root->left);
    else return find_node(root->right);


}

and the call to this method would look like:

//Assuming that the root is the root node of the tree.
std::stack<node*> s;
node* ptr = find_node(root,s); // we have all ancestors of ptr along with ptr in stack s now.

b. Now we have to print next k immediate bigger (than ptr) elements of the tree. We will start from the node (which is ptr) :

// s  -> stack of all ancestor of the node.
// k  -> k-successor to find.
void print_k_bigger(stack<node*> s, int k)
{
    //since the top element of stack is the starting node. So we won't print it.
    // We will just pop the first node and perform inorder traversal on its right child.
    prev = s.top();
    s.pop();
    inorder(prev->right,k);

    // Now all the nodes present in the stack are ancestor of the node. 
    while(!s.empty() && k>0)
    {
        //pop the node at the top of stack.
        ptr = s.top();
        s.pop();

        //if the node popped previously (prev) was the right child of the current 
        //node, i.e. prev was bigger than the current node. So we will have to go 
        //one more level up to search for successors. 
        if(prev == ptr->right) continue;

        //else the current node is immidiate bigger than prev node. Print it.
        printf("%d",ptr->value);

        //reduce the count.
        k--;

        //inorder the right subtree of the current node.
        inorder(ptr->right);

        //Note this.
        prev = ptr;

    }

}

Here is how our inorder will look like:

void inorder(node* ptr, int& k)
{
    if(ptr != NULL)
    {
        inorder(ptr->left,k);
        printf("%d",ptr->value);
        k--;
        inorder(ptr->right,k);
    }
}

Time Analysis:

  1. The method find_node is O(h) as it can go upto the length max root to leaf path.
  2. The method print_k_bigger is O(h+k) as in every iteration of the loop, either the size of stack is reducing or the value of k is reducing. Note that when inorder() is called from inside the while loop, it won't raise additional overhead as all the calls to inorder() together will take at max O(k).
Stickseed answered 12/11, 2016 at 15:22 Comment(2)
This is a good answer, but I think it's too detailed and might confuse the OP, who just wants to understand the time complexity.Menopause
I also don't think the OP was looking for an implementation or how to get the job done, although providing code may help to see the time complexity. The first reference to ptr->right probably needs to be prev->right. The phrase "it won't raise additional overhead as all the calls to inorder() together will take at max O(k)." is essential, and I think the OP would be focussing on that aspect. But I am not the OP :-) You have my vote.Visitant
M
1

Here is a very simple algorithm to do this.

Create an empty stack S and a variable curr = NULL.

  1. Find the starting node in the tree. Also, push all of it's ancestors (and the node itself) into stack S
  2. Now pop a node top from the stack S and check if curr is it's right child. If it is not do an inorder traversal of it's right subtree
    • If we find k or more nodes while traversing, we are done
  3. If we haven't discovered k successors yet, set curr = top, and repeat 2 till we find k nodes

Overall time compleity is O(h+k). Step 1 takes O(h) time. Step 2 takes O(h+k) time (all iterations of step 2 combined take O(h+k) time.)!

Menopause answered 12/11, 2016 at 9:13 Comment(2)
Looks like your explanation needs some improvement. What if the previously popped element was the right child of the currently popped element ? Check out my answer for better explanation!!!Stickseed
@Stickseed You are correct. It's a trivial check though. Updated my answer.Menopause

© 2022 - 2024 — McMap. All rights reserved.