A Tree-based Heap
A Tree-based Heap
In the figures in this chapter we’ve shown heaps as if they were trees because it’s easier to visualize them that way, but the implementation has been based on an array. However, it’s possible to use an actual tree-based implementation. The tree will
be a binary tree, but it won’t be a search tree because, as we’ve seen, the ordering principle is not that strong. It will also be a complete tree, with no missing nodes. Let’s call such a tree a tree heap.
One problem with tree heaps is finding the last node. You need to find this node to remove the maximum item, because it’s the last node that’s inserted in place of the deleted root (and then trickled down). You also need to find the first empty node, because that’s where you insert a new node (and then trickle it up). You can’t search for these nodes because you don’t know their values, and anyway it’s not a search tree. However, these locations are not hard to find in a complete tree if you keep track of the number of nodes in the tree.
As we saw in the discussion of the Huffman tree in Chapter 8, you can represent the path from root to leaf as a binary number, with the binary digits indicating the path from each parent to its child: 0 for left and 1 for right.
It turns out there’s a simple relationship between the number of nodes in the tree and the binary number that codes the path to the last node. Assume the root is numbered 1; the next row has nodes 2 and 3; the third row has nodes 4, 5, 6, and 7; and so on. Start with the node number you want to find. This will be the last node or the first null node. Convert the node number to binary. For example, say there are
29 nodes in the tree and you want to find the last node. The number 29 decimal is 11101 binary. Remove the initial 1, leaving 1101. This is the path from the root to node 29: right, right, left, right. The first available null node is 30, which (after removing the initial 1) is 1110 binary: right, right, right, left.
To carry out the calculation, you can repeatedly use the % operator to find the
Heapsort 601
operator to actually divide n by 2. When n is less than 1, you’re done. The sequence of remainders, which you can save in an array or string, is the binary number. (The least significant bits correspond to the lower end of the path.) Here’s how you might calculate it:
while(n >= 1) { path[j++] = n % 2; n = n / 2; }
You could also use a recursive approach in which the remainders are calculated each time the function calls itself and the appropriate direction is taken each time it returns.
After the appropriate node (or null child) is found, the heap operations are fairly straightforward. When trickling up or down, the structure of the tree doesn’t change, so you don’t need to move the actual nodes around. You can simply copy the data from one node to the next. This way, you don’t need to connect and disconnect all the children and parents for a simple move. The Node class will need a field for the parent node because you’ll need to access the parent when you trickle up. We’ll leave the implementation of the tree heap as a programming project.
The tree heap operates in O(logN) time. As in the array-based heap the time is mostly spent doing the trickle-up and trickle-down operations, which take time proportional to the height of the tree.