8.4.7 An Implementation for Heapsort

A detailed version of the heapsort algorithm can now be developed. You have now seen a number of ways to create a heap and to reheap. We will use the second heap creation algorithm for phase I and the second reheaping algorithm for phase II. Both algorithms use the shift function.

To proceed we must decide on an implementation of the binary tree data structure. The binary tree used in heapsort is initially complete. The required processing involves predecessors and successors of nodes. Sequential representation for the binary tree is ideal, so we use it. The binary tree is stored in the data array and consists of n records.

Storage is needed for the sorted version of data. Although another array may seem necessary for this purpose, we will use only the data array. The algorithm, after creating a heap (in data), continually takes the root record as the next record in the sorted output and then reheaps. Before reheaping, the rightmost record at the lowest level of the heap is placed at the root. The storage vacated by that rightmost record will no longer be needed. We choose to store the output record there. You should convince yourself that this will result in the records appearing in data in reverse sorted order upon termination. To remedy this, we write shift so that it produces min heaps. The records will then appear in correct sorted order upon termination.

Shift has parameters data, root, and last. Root points to the root of a subtree, and last points to the node of the original heap that is currently the rightmost node at greatest depth. The nodes last+1 to n are currently storing the already sorted n-last output records. Whenever shift is invoked, the left and right subtrees of root (excluding nodes last+1 to n) are assumed to be heaps. Shift returns after reheaping, by allowing the root record to move down to its final position. This is done by first storing the key value and record of the initial root record in keyvalue and recordvalue, respectively. Ptr points to the node currently being considered as the final position for the initial root record. This will be the final position if either of the following two conditions is met.

Figure 8.13 (a) Initial Heap and (b) Heap after First Three Entries Have Been Output

1. The successor record of ptr with minimum key value has a key value greater than or equal to keyvalue, or

2. Ptr points to a terminal node or to a node whose successors are nodes among last+1 to n.

Condition 2 can be recognized by the test (succ<=last), where succ points to the left successor node of ptr. Condition 1 requires finding the minimum key value for the successors of ptr. Ptr may not have a right successor, or that successor may represent an output record. This can be tested for by the test (succ<last).

Copy copies the contents of the record pointed to by its second parameter into the record pointed to by its first parameter. When shift determines that ptr is not the final position, it moves the record at ptr to its predecessor node. This is why the original record at root is saved. When the final position is found, the saved record is placed there. The initial heap and the situation after the first three entries have been output are shown in Figure 8.13 for the tree of Figure 8.7.

The implementation for heapsort follows.

#define MAX 100

typedef struct

{

int key;

}record;

typedef record recordarray[MAX];

heapsort (data,n)

/* Sorts the n records stored in array data

in descending order.

*/

recordarray data;

int n;

{

int root,last;

>Comment

root = n/2;

last = n;

while (root > 0)

{

>Comment

shift(data,root,last);

root--;

}

root = 1;

while (last > 1)

{

>Comment

interchange(data,1,last);

last--;

shift(data,root,last);

}

}

shift (data,root,last)

/* Makes the subtree of root into

a heap (min heap). The left and

right subtrees of root must be

heaps. All subtrees are stored

in array data in sequential

representation. Only the nodes

between root and last are

considered to be in the subtrees.

*/

recordarray data;

int root,last;

{

>Comment

int ptr,succ;

int keyvalue;

record recordvalue;

>Comment

ptr = root;

>Comment

succ = 2 * ptr;

>Comment

if (succ < last)

if (data[succ + 1].key < data[succ].key)

>Comment

succ++;

at this point, succ points to the smallest of ptr's successors

>Comment

keyvalue = data[root].key;

>Comment

copy(&recordvalue,&data[root]);

the purpose of the while loop is to determine the proper place for the original root record; ptr will

point to that location when the loop is exited

>Comment

while((succ <= last) && (data[succ].key < keyvalue))

{

>Comment

copy(&data[ptr],&data[succ]);

>Comment

ptr = succ;

>Comment

succ = 2 * ptr

if (succ < last)

if (data[succ + 1].key < data[succ].key)

succ++;

}

>Comment

copy(&data[ptr],&recordvalue);

}