Enough pseudocode was given in section 15.3.2 to allow the following heavily commented implementation to stand on its own.
IMPLEMENTATION MODULE Heaps; (****************** Design by R. Sutcliffe copyright 1996 Modified 1996 10 16 This module provides a Heap ADT. ******************) FROM Storage IMPORT ALLOCATE, DEALLOCATE; FROM DataADT IMPORT DataType, Assign, GetKey, ActionProc, Compare, CompareResults; TYPE NodePointer = POINTER TO TreeNode; TreeNode = RECORD dataItem : DataType; leftPoint, rightPoint, parent, (* binary tree linkage *) next, prev : NodePointer; (* linear linkage across rows *) END; Heap = POINTER TO TreeData; TreeData = RECORD root, (* first node *) last, (* last node *) lowerLeft (* first node in last row; helps for adding linkage to next row *) : NodePointer; state : HeapState; (* stores error conditions *) travKind : TraverseKind; (* inOrder, preOrder, postOrder or rowOrder *) travDirIsForward : BOOLEAN; room, (* how many could be stored if last row full *) numItems (* how many are actually stored *) : CARDINAL; END; PROCEDURE MakeNode () : NodePointer; (* set up one new node with all nil pointers and no data; return a pointer to the new node. *) VAR temp : NodePointer; BEGIN NEW (temp); (* get node memory *) IF temp # NIL THEN temp^.leftPoint := NIL; temp^.rightPoint := NIL; temp^.parent := NIL; temp^.next := NIL; temp^.prev := NIL; END; RETURN temp; END MakeNode; PROCEDURE KillNode (VAR node : NodePointer); (* give back all memory associated with node *) BEGIN IF node # NIL THEN DISPOSE (node); END; END KillNode; PROCEDURE Erase (VAR r : NodePointer); (* Pre: r is the root of a subtree Post: recursive post traverse killing all nodes below as well as the one passed in *) BEGIN IF r # NIL THEN Erase (r^.leftPoint); Erase (r^.rightPoint); KillNode (r); END; END Erase; (* It turned out the following was not needed, but who knows; why not leave it. *) PROCEDURE IsLeaf (VAR node : NodePointer) : BOOLEAN; BEGIN RETURN (node # NIL) AND (node^.leftPoint = NIL); (* don't care about right *) END IsLeaf; PROCEDURE FindKey (node : NodePointer; key : KeyType; VAR result : NodePointer) : BOOLEAN; (* start at the given node and go looking for the data with the given key. If found, return both a pointer to it and TRUE; if not found, return FALSE. Recursive preorder traversal *) BEGIN IF node = NIL (* safety measure *) THEN RETURN FALSE; (* look at node data first *) ELSIF Compare (GetKey(node^.dataItem), key) = equal THEN result := node; RETURN TRUE; (* then at the left subtree *) ELSIF FindKey (node^.leftPoint, key, result) THEN RETURN TRUE; (* and at the right one *) ELSE RETURN FindKey (node^.rightPoint, key, result) END; END FindKey; PROCEDURE TraverseRows (heap : Heap; Proc : ActionProc); (* Traverse the tree row by row, that is, using the linear linkage doing the procedure on each data item *) VAR count : CARDINAL; node: NodePointer; BEGIN IF heap^.travDirIsForward THEN (* start at the root *) count := 0; node := heap^.root; (* and work consecutively through the noides *) WHILE count < heap^.numItems DO INC (count); Proc (node^.dataItem); node := node^.next; END; (* while *) ELSE (* go in reverse order *) count := heap^.numItems; node := heap^.last; WHILE count > 0 DO DEC (count); Proc (node^.dataItem); node := node^.prev; END; (* while *) END (*if *) END TraverseRows; PROCEDURE ForwardTraverseNodes (node : NodePointer; tKind : TraverseKind; Proc : ActionProc); (* These are the forward recursive tree traverse routines. Call with the root to traverse the whole tree. *) BEGIN IF node = NIL (* safety measure *) THEN RETURN (* if at leaf then process it and report up *) ELSIF node^.leftPoint = NIL THEN Proc (node^.dataItem); RETURN; (* not at leaf so kick in recursion *) ELSE CASE tKind OF inOrder: ForwardTraverseNodes (node^.leftPoint, tKind, Proc); Proc (node^.dataItem); ForwardTraverseNodes (node^.rightPoint, tKind, Proc);| preOrder: Proc (node^.dataItem); ForwardTraverseNodes (node^.leftPoint, tKind, Proc); ForwardTraverseNodes (node^.rightPoint, tKind, Proc);| postOrder: ForwardTraverseNodes (node^.leftPoint, tKind, Proc); ForwardTraverseNodes (node^.rightPoint, tKind, Proc); Proc (node^.dataItem); ELSE (* row order is handled elsewhere *) END; (* case *) END; (* if *) END ForwardTraverseNodes; PROCEDURE ReverseTraverseNodes (node : NodePointer; tKind : TraverseKind; Proc : ActionProc); (* These are the reverse recursive tree traverse routines. Call with the root to traverse the whole tree. *) BEGIN IF node = NIL (* safety measure *) THEN RETURN (* if at leaf then process it and report up *) ELSIF node^.leftPoint = NIL THEN Proc (node^.dataItem); RETURN; (* not at leaf so kick in recursion *) ELSE CASE tKind OF inOrder: ReverseTraverseNodes (node^.rightPoint, tKind, Proc); Proc (node^.dataItem); ReverseTraverseNodes (node^.leftPoint, tKind, Proc);| preOrder: Proc (node^.dataItem); ReverseTraverseNodes (node^.rightPoint, tKind, Proc); ReverseTraverseNodes (node^.leftPoint, tKind, Proc);| postOrder: ReverseTraverseNodes (node^.rightPoint, tKind, Proc); ReverseTraverseNodes (node^.leftPoint, tKind, Proc); Proc (node^.dataItem); ELSE (* row order is handled elsewhere *) END; (* case *) END; (* if *) END ReverseTraverseNodes; PROCEDURE SiftUp (node : NodePointer); (* Sift a data item up through heap until it is a proper parent. If it is already in the right place, nothing happens. *) VAR data : DataType; BEGIN (* set data item from node aside *) Assign (node^.dataItem, data); (* see if it needs to go up the tree *) WHILE (node^.parent # NIL) AND (Compare (data, node^.parent^.dataItem) = less) DO (* yes, so move parent down and look higher *) Assign (node^.parent^.dataItem, node^.dataItem); node := node^.parent; END; (* put data into proper place *) Assign (data, node^.dataItem); END SiftUp; PROCEDURE SiftDown (node : NodePointer); (* Sift data item down through heap until it is a proper child. If it is already in the right place, nothing happens. *) VAR data : DataType; BEGIN (* set data item from node aside *) Assign (node^.dataItem, data); (* see if it needs to go down the tree *) WHILE ((node^.leftPoint # NIL) AND (Compare (data, node^.leftPoint^.dataItem) = greater)) OR ((node^.rightPoint # NIL) AND (Compare (data, node^.rightPoint^.dataItem) = greater)) DO (* pull up smaller child until it is a proper child *) (* yes, so move smaller child up and look lower *) IF (node^.rightPoint = NIL) OR (Compare (node^.leftPoint^.dataItem, node^.rightPoint^.dataItem) # greater) THEN Assign (node^.leftPoint^.dataItem, node^.dataItem); node := node^.leftPoint; ELSE Assign (node^.rightPoint^.dataItem, node^.dataItem); node := node^.rightPoint; END; END; (* put data into proper place *) Assign (data, node^.dataItem); END SiftDown; (************************************ Exported Procedures ************************************) PROCEDURE Status (heap : Heap) : HeapState; (* Pre : t is a valid initialized heap Post : The State of the heap is returned *) BEGIN RETURN heap^.state; END Status; PROCEDURE Init (VAR heap : Heap); (* Allocates memory for a new heap sets state to allRight *) BEGIN NEW (heap); IF heap # NIL THEN heap^.state := allRight; heap^.root := NIL; heap^.last := NIL; heap^.lowerLeft := NIL; heap^.numItems := 0; heap^.room := 0; heap^.travKind := inOrder; heap^.travDirIsForward := TRUE; END; END Init; PROCEDURE Destroy (VAR heap : Heap); (* disposes the whole heap *) BEGIN Erase (heap^.root); (* all nodes *) DISPOSE (heap); (* tree data *) END Destroy; PROCEDURE Add (VAR heap : Heap; data : ItemType); (* Adds a new item to the heap. If successful sets state to allRight, else to enheapFailed *) VAR temp, mom : NodePointer; BEGIN IF heap # NIL THEN (* make a new node to hold the data *) temp := MakeNode(); IF temp # NIL THEN (* stuff data in node *) Assign (data, temp^.dataItem); INC (heap^.numItems); IF heap^.numItems = 1 THEN (* we just made a root *) heap^.root := temp; heap^.lowerLeft := temp; heap^.last := temp; heap^.room := 1; RETURN; ELSIF heap^.numItems > heap^.room THEN (* need to make new row *) mom := heap^.lowerLeft; heap^.lowerLeft := temp; heap^.room := 2*heap^.room + 1; ELSE (* continue on the same row *) (* either the parent can take a new right child *) mom := heap^.last^.parent; IF mom ^.rightPoint # NIL THEN (* or the next one on the row can -- not at end *) mom := mom^.next; END; END; (* if heap *) (* now set up all the rest of the linkage *) temp^.parent := mom; IF mom ^.leftPoint = NIL THEN mom^.leftPoint := temp; ELSE mom^.rightPoint := temp; END; (* if mom *) heap^.last^.next := temp; temp^.prev := heap^.last; heap^.last := temp; (* ensure data goes to right ancestral node *) SiftUp (temp); heap^.state := allRight; ELSE (* couldn't get node room *) heap^.state := enheapFailed; END (* if temp *) ELSE (* heap itself is NIL *) heap^.state := enheapFailed; END; (* if heap *) END Add; PROCEDURE Delete (VAR heap : Heap; key : KeyType); (* deletes an item whose key is defined equal to _key_ from the heap. If successful sets state to allRight; if heap was empty sets state to empty *) VAR targetNode, temp : NodePointer; lastData : DataType; BEGIN (* find the node with the data if it is there *) IF heap^.numItems = 0 THEN (* can't delete from an empty heap so set flag *) heap^.state := empty; RETURN; ELSE (* whether we find an item to delete does not matter *) heap^.state := allRight; END; (* ok so go out and look for it *) IF FindKey (heap^.root, key, targetNode) THEN temp := heap^.last; (* save data from end of heap *) (* now fix all the pointers at the end to delete that last node *) lastData := temp^.dataItem; heap^.last := temp^.prev; IF temp^.parent^.leftPoint = temp THEN temp^.parent^.leftPoint := NIL; ELSE temp^.parent^.rightPoint := NIL; END; (* if temp^ *) DEC (heap^.numItems); (* check to see if must shrink number of levels *) IF heap^.numItems = heap^.room DIV 2 THEN (* must have killed first item in row, so *) heap^.lowerLeft := temp^.parent; heap^.room := heap^.numItems; END; (* if heap^ *) IF temp # targetNode (* if it is, we're done *) THEN (* pop the data item from last into node of data to delete *) Assign (lastData, targetNode^.dataItem); (* then see if it needs moving up or down *) (* only one of the following will do anything *) SiftDown (targetNode); SiftUp (targetNode); END; (* if temp *) (* finally, dump memory from the last node *) KillNode (temp); ELSE (* if FindKey *) (* nothing. If data not found we just don't care.*) END; (* if FindKey *) END Delete; PROCEDURE Search (heap : Heap; key : KeyType; VAR data : ItemType) : BOOLEAN; (* if found, returns TRUE and _data_ returns item at that point *) VAR temp : NodePointer; BEGIN IF (heap^.root # NIL) AND (heap^.numItems # 0) AND (FindKey (heap^.root, key, temp)) THEN data := temp^.dataItem; RETURN TRUE; ELSE RETURN FALSE; END; END Search; PROCEDURE SetTraverseKind (heap : Heap; tKind : TraverseKind); (* The default is inorder *) BEGIN IF heap # NIL THEN heap^.travKind := tKind; END; END SetTraverseKind; PROCEDURE ReverseTraverseDirection (heap : Heap); (* The default is forward, but this can be changed to and fro. The user has to keep track. *) BEGIN IF heap # NIL THEN heap^.travDirIsForward := ~heap^.travDirIsForward; END; END ReverseTraverseDirection; PROCEDURE Size (heap : Heap) : CARDINAL; (* Pre : heap is a valid initialized Heap Post: The number of data items in the heap is returned *) BEGIN RETURN heap^.numItems END Size; PROCEDURE Traverse (heap : Heap; Proc : ActionProc); (* Pre : heap is a valid initialized Heap Post : the nodes are traversed inorder and Proc is performed on each data item. *) VAR temp : NodePointer; BEGIN IF (heap^.root # NIL) AND (heap^.numItems # 0) THEN (* special case the linear, nonrecursive traverses *) IF heap^.travKind = rowOrder THEN TraverseRows (heap, Proc); ELSIF heap^.travDirIsForward THEN ForwardTraverseNodes (heap^.root, heap^.travKind, Proc); ELSE ReverseTraverseNodes (heap^.root, heap^.travKind, Proc); END; END; END Traverse; END Heaps.
The same cardinal ADT was used in the testing of this module as in the testing of the B-tree module. In addition, the following program module was written to check the implementation and ensure that it was correct. It should be studied carefully for completeness. The data used is that shown above in the discussion of heaps.
MODULE TestHeaps; (* A simple program to test the Heaps library module. by R. Sutcliffe last modified 1996 10 18 *) IMPORT Heaps, DataADT, SWholeIO, STextIO; FROM Heaps IMPORT TraverseKind; VAR theHeap : Heaps.Heap; sum : CARDINAL; dataRet: DataADT.DataType; PROCEDURE Summit (item : DataADT.DataType); (* a procedure to use in a test traverse *) BEGIN sum := sum + DataADT.GetKey (item) END Summit; (* The following procedures are used to print out the tree looking a little like a tree *) PROCEDURE WriteSpace (n:CARDINAL); (* write a specified number of spaces *) VAR count : CARDINAL; BEGIN FOR count := 1 TO n DO STextIO.WriteChar (" "); END; END WriteSpace; (* these need to be global as both procs manipulate them *) VAR count, rowEnd, space : CARDINAL; PROCEDURE AltWriteData ( item : DataADT.DataType); (* write out a heap item followed by some space. If at row end, start a new row and adjust spacing for that row. *) BEGIN IF count = rowEnd THEN STextIO.WriteLn; space := space DIV 2; IF space # 0 THEN WriteSpace (space-1); END; rowEnd := rowEnd*2 +1; END; DataADT.WriteData (item); INC (count); IF (space # 0) AND (count # rowEnd) THEN WriteSpace (2*space-1); END; END AltWriteData; PROCEDURE WriteHeap ( heap : Heaps.Heap); (* Writes a heap in a way that resembles a tree. Won't work very well except to write a number, say a key. *) VAR size : CARDINAL; BEGIN Heaps.SetTraverseKind (theHeap,rowOrder); (* compute spacing parameters based on size of heap *) size := Heaps.Size(heap); space := 1; WHILE space <= size DO space := 2 * space; END; (* so, it's empirical. Experiment. *) space := 2 * space - 1; count := 0; rowEnd := 0; Heaps.Traverse (heap, AltWriteData); STextIO.WriteLn; STextIO.WriteLn; END WriteHeap; BEGIN Heaps.Init (theHeap); Heaps.Add (theHeap, 54);WriteHeap (theHeap); Heaps.Add (theHeap, 87);WriteHeap (theHeap); Heaps.Add (theHeap, 27);WriteHeap (theHeap); Heaps.Add (theHeap, 67);WriteHeap (theHeap); Heaps.Add (theHeap, 19);WriteHeap (theHeap); Heaps.Add (theHeap, 31);WriteHeap (theHeap); Heaps.Add (theHeap, 29);WriteHeap (theHeap); Heaps.Add (theHeap, 18);WriteHeap (theHeap); Heaps.Add (theHeap, 32);WriteHeap (theHeap); Heaps.Add (theHeap, 56);WriteHeap (theHeap); Heaps.Add (theHeap, 7);WriteHeap (theHeap); Heaps.Add (theHeap, 12);WriteHeap (theHeap); Heaps.Add (theHeap, 31);WriteHeap (theHeap); STextIO.WriteString ("*****forward traverses****"); STextIO.WriteLn; Heaps.SetTraverseKind (theHeap,inOrder); STextIO.WriteString ("in :"); Heaps.Traverse (theHeap, DataADT.WriteData);STextIO.WriteLn; Heaps.SetTraverseKind (theHeap,preOrder); STextIO.WriteString ("pre :"); Heaps.Traverse (theHeap, DataADT.WriteData);STextIO.WriteLn; Heaps.SetTraverseKind (theHeap,postOrder); STextIO.WriteString ("post:"); Heaps.Traverse (theHeap, DataADT.WriteData);STextIO.WriteLn; Heaps.SetTraverseKind (theHeap,rowOrder); STextIO.WriteString ("row :"); Heaps.Traverse (theHeap, DataADT.WriteData);STextIO.WriteLn; Heaps.SetTraverseKind (theHeap,inOrder); STextIO.WriteString ("****end forward traverses*****"); STextIO.WriteLn;STextIO.WriteLn; Heaps.ReverseTraverseDirection(theHeap); STextIO.WriteString ("*****reverse traverses****"); STextIO.WriteLn; Heaps.SetTraverseKind (theHeap,inOrder); STextIO.WriteString ("in :"); Heaps.Traverse (theHeap, DataADT.WriteData);STextIO.WriteLn; Heaps.SetTraverseKind (theHeap,preOrder); STextIO.WriteString ("pre :"); Heaps.Traverse (theHeap, DataADT.WriteData);STextIO.WriteLn; Heaps.SetTraverseKind (theHeap,postOrder); STextIO.WriteString ("post:"); Heaps.Traverse (theHeap, DataADT.WriteData);STextIO.WriteLn; Heaps.SetTraverseKind (theHeap,rowOrder); STextIO.WriteString ("row :"); Heaps.Traverse (theHeap, DataADT.WriteData);STextIO.WriteLn; Heaps.SetTraverseKind (theHeap,inOrder); STextIO.WriteString ("****end reverse traverses*****"); STextIO.WriteLn;STextIO.WriteLn; (* look for something that is supposed to be there *) IF Heaps.Search (theHeap,31,dataRet) THEN STextIO.WriteString ("data found OK as "); DataADT.WriteData (dataRet); ELSE STextIO.WriteString ("31 not found"); END; STextIO.WriteLn; STextIO.WriteLn; (* and for something that is not *) IF Heaps.Search (theHeap,100,dataRet) THEN STextIO.WriteString ("data found OK as "); DataADT.WriteData (dataRet); ELSE STextIO.WriteString ("100 not found"); END; STextIO.WriteLn;STextIO.WriteLn; (* now traverse the heap and add everything up *) sum := 0; Heaps.Traverse (theHeap, Summit); STextIO.WriteLn; STextIO.WriteString ("Sum is "); SWholeIO.WriteCard (sum, 10); STextIO.WriteLn;STextIO.WriteLn; (* now, try a few deletes *) Heaps.ReverseTraverseDirection(theHeap); Heaps.Delete (theHeap, 31);WriteHeap (theHeap); Heaps.Delete (theHeap, 67);WriteHeap (theHeap); Heaps.Delete (theHeap, 19);WriteHeap (theHeap); Heaps.Delete (theHeap, 7);WriteHeap (theHeap); Heaps.Delete (theHeap, 42);WriteHeap (theHeap); Heaps.Add (theHeap, 12); WriteHeap (theHeap); END TestHeaps
When this program was run, the following output was collected. The reader should verify that the traverses are in fact all correct.
54 54 87 27 87 54 27 67 54 87 19 27 54 87 67 19 27 31 87 67 54 19 27 29 87 67 54 31 18 19 29 27 67 54 31 87 18 19 29 27 67 54 31 87 32 18 19 29 27 56 54 31 87 32 67 7 18 29 27 19 54 31 87 32 67 56 7 18 12 27 19 29 31 87 32 67 56 54 7 18 12 27 19 29 31 87 32 67 56 54 31 *****forward traverses**** in : 87 27 32 18 67 19 56 7 54 29 31 12 31 pre : 7 18 27 87 32 19 67 56 12 29 54 31 31 post: 87 32 27 67 56 19 18 54 31 29 31 12 7 row : 7 18 12 27 19 29 31 87 32 67 56 54 31 ****end forward traverses***** *****reverse traverses**** in : 31 12 31 29 54 7 56 19 67 18 32 27 87 pre : 7 12 31 29 31 54 18 19 56 67 27 32 87 post: 31 31 54 29 12 56 67 19 32 87 27 18 7 row : 31 54 56 67 32 87 31 29 19 27 12 18 7 ****end reverse traverses***** data found OK as 31 100 not found Sum is 470 7 18 12 27 19 29 31 87 32 67 56 54 7 18 12 27 19 29 31 87 32 54 56 7 18 12 27 54 29 31 87 32 56 12 18 29 27 54 56 31 87 32 12 18 29 27 54 56 31 87 32 12 12 29 27 18 56 31 87 32 54