Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

K-th element in a heap tree

I have a heap (implemented like a binary tree: each node has two pointers to the children and one pointer to the parent).

How can I find the k-th element (in a BFS order), given the number of elements in it? I think it can be done in O(logn) time..

like image 563
Settembre Nero Avatar asked Dec 12 '12 18:12

Settembre Nero


People also ask

What is the kth element?

kth smallest element is the minimum possible n such that there are at least k elements in the array <= n. In other words, if the array A was sorted, then A[k - 1] ( k is 1 based, while the arrays are 0 based ) NOTE.

How do you find the kth largest element in a max heap?

Using Max Heap log(n)) by using a max-heap. The idea is to simply construct a max-heap of size n and insert all the array elements [0…n-1] into it. Then pop first k-1 elements from it. Now k'th largest element will reside at the root of the max-heap.

What is a K ary heap?

(data structure) Definition: A complete tree where every node has a key more extreme (greater or less) than the key of its parent. Each node has k or fewer children.

What is the time complexity of heap?

In summary, the work for heap sort is the sum of the two stages: O(n) time for buildHeap and O(n log n) to remove each node in order, so the complexity is O(n log n).

What is the maximum element in a max heap ()?

In a max-heap, the parent or root node is usually greater than the children nodes. The maximum element can be accessed in constant time since it is at index 1 . Based on the figure above, at every level, the largest number is the parent node.


1 Answers

(I'm assuming by "kth element (in a BFS order)" that you mean the kth element from the perspective of a top-to-bottom, left-to-right scan of the input.)

Since you know that a binary heap is a complete binary tree (except possibly at the last level), you know that the shape of the tree is a perfect binary tree of some height (containing 2k nodes for some k) with some number of nodes filled in from the left to the right. A really nifty property of these trees occurs when you write out the numbers of the nodes in a picture, one-indexing the values:

                      1
            2                3
        4       5        6       7
       8 9    10 11    12 13   14 15

Notice that each layer starts with a node that's a power of two. So let's suppose, hypothetically, that you wanted to look up the number 13. The biggest power of two no greater than 13 is 8, so we know that 13 must appear in the row

  8  9 10 11 12 13 14 15

We can now use this knowledge to reverse-engineer the path from 13 back up to the root of the tree. We know, for example, that 13 is in the latter half of the the numbers in this row, which means that 13 belongs to the right subtree of the root (if it belonged to the left subtree, then we would be in the subtree containing 8, 9, 10, and 11.) This means that we can go right from the root and throw out half of the numbers to get

12 13 14 15

We are now at node 3 in the tree. So do we go left or right? Well, 13 is in the first half of these numbers, so we know at this point that we need to descend into the left subtree of node 3. This takes us to node 6, and now we're left with the first half of the numbers:

12 13

13 is in the right half of these nodes, so we should descend to the right, taking us to node 13. And voila! We're there!

So how did this process work? Well, there's a really, really cute trick we can use. Let's write out the same tree we had above, but in binary:

                        0001
            0010                    0011
      0100        0101        0110        0111
   1000  1001  1010  1011  1100  1101  1110  1111
                                 ^^^^

I've pointed out the location of node 13. Our algorithm worked in the following way:

  1. Find the layer containing the node.
  2. While not at the node in question:
    1. If the node is in the first half of the layer it's in, move left and throw away the right half of the range.
    2. If the node is in the second half of the layer it's in, move right and throw away the left half of the range.

Let's think about what this means in binary. Finding the layer containing the node is equivalent to finding the most significant bit set in the number. In 13, which has binary representation 1101, the MSB is the 8 bit. This means that we're in the layer starting with eight.

So how do we determine whether we're in the left subtree or the right subtree? Well, to do that, we'd need to see if we are in the first half of this layer or the second half. And now for a cute trick - look at the next bit after the MSB. If this bit is set to 0, we're in the first half of the range, and otherwise we're in the second half of the range. Thus we can determine which half of the range we're in by just looking at the next bit of the number! This means we can determine which subtree to descend into by looking just at the next bit of the number.

Once we've done that, we can just repeat this process. What do we do at the next level? Well, if the next bit is a zero, we go left, and if the next bit is a one, we go right. Take a look at what this means for 13:

 1101
  ^^^
  |||
  ||+--- Go right at the third node.
  ||
  |+---- Go left at the second node.
  |
  +----- Go right at the first node.

In other words, we can spell out the path from the root of the tree to our node in question just by looking at the bits of the number after the MSB!

Does this always work! You bet! Let's try the number 7. This has binary representation 0111. The MSB is in the 4's place. Using our algorithm, we'd do this:

0111
  ^^
  ||
  |+--- Go right at the second node.
  |
  +---- Go right at the first node.

Looking in our original picture, this is the right path to take!

Here's some rough C/C++ pseudocode for this algorithm:

Node* NthNode(Node* root, int n) {
    /* Find the largest power of two no greater than n. */
    int bitIndex = 0;
    while (true) {
        /* See if the next power of two is greater than n. */
        if (1 << (bitIndex + 1) > n) break;
        bitIndex++;
    }

    /* Back off the bit index by one.  We're going to use this to find the
     * path down.
     */
    bitIndex--;

    /* Read off the directions to take from the bits of n. */
    for (; bitIndex >= 0; bitIndex--) {
        int mask = (1 << bitIndex);
        if (n & mask)
            root = root->right;
        else
            root = root->left;
    }
    return root;
}

I haven't tested this code! To paraphrase Don Knuth, I've just shown that conceptually it does the right thing. I might have an off-by-one error in here.

So how fast is this code? Well, the first loop runs until it finds the first power of two greater than n, which takes O(log n) time. The next part of the loop counts backwards through the bits of n one at a time, doing O(1) work at each step. The overall algorithm thus takes a total of O(log n) time.

Hope this helps!

like image 167
templatetypedef Avatar answered Oct 27 '22 00:10

templatetypedef