(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:
- Find the layer containing the node.
- While not at the node in question:
- 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.
- 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!