As in previous examples, the concern here will not be with the data to be placed in the tree, but with the mechanism for implementing the tree structure itself. This can be done in the same semi-generic fashion as in other examples in the text. Since there is already on hand (in the form of the module Countries) a suitable data type, the module here is called CountryBinaryTree. The usual minor renaming is needed to use any other ADT in the place of Country.
In this version, the three kinds of traverse are distinguished via an enumeration, a parameter of which type must be passed to determine which traverse to perform with the procedure acting on the data items.
DEFINITION MODULE CountryBinaryTree; (* semi-generic tree type by R. Sutcliffe last modified 1995 06 07 *) FROM Countries IMPORT Country, KeyType, ActionProc; TYPE DataType = Country; (* change this line and import as needed *) TreeState = (allRight, empty, entreeFailed, notFound, bad); BinaryTree; (* opaque type *) TraverseOrder = (in, pre, post); PROCEDURE TreeStatus (t : BinaryTree) : TreeState; (* Pre : t is a valid initialized table Post : The State of the tree is returned *) PROCEDURE Create (VAR t : BinaryTree); (* Pre : none Post : t is a newly created empty tree.*) PROCEDURE Insert (t : BinaryTree; data : DataType); (* Pre : t is a valid initialized tree Post : memory is obtained and data has been entreed in the proper place for a binary tree using the ADT compare procedure and the state of the tree is allRight or the entreeing failed and the state is entreeFailed. *) PROCEDURE Fetch (t : BinaryTree; key : KeyType; VAR data : DataType); (* Pre : t is a valid initialized tree Post : data matching key is returned in data and the state of the tree is allRight or the fetch failed and the state is notFound. *) PROCEDURE Update (t : BinaryTree; data : DataType); (* Pre : t is a valid initialized tree Post : data matching the key of the data is updated in the tree and the state of the tree is allRight or the update failed and the state is notFound. *) PROCEDURE Remove (t : BinaryTree; key : KeyType; VAR data : DataType); (* Pre : t is a valid initialized tree Post : data matching key has been removed and returned in data (not disposed of) and the state of the tree is allRight or the removal failed and the state is notFound. *) PROCEDURE Destroy (VAR t : BinaryTree); (* Pre : t is a valid initialized tree Post : the tree memory is returned and the variable is invalid and the memory associated with the items in the tree is removed by calling the ADT module dispose procedure. *) PROCEDURE Traverse (t : BinaryTree; Proc : ActionProc; order : TraverseOrder); (* Pre : t is a valid initialized tree Post : the table items are traversed in the order given and Proc is performed on each one. *) END CountryBinaryTree.
When this was implemented, a number of local procedures were developed, most of which act on nodes rather than on the data in the nodes. Such actions need to be hidden from the outside world, and separating them even from the abstract (and somewhat generic) procedures that handle data is recommended. The reader should note that insertion of new items always takes place at a leaf, but deletion at other than a leaf position is rather complex. If the node to be deleted has only one subtree hanging from it, then that subtree can be drawn up to take its place, as shown in figure 14.6.
If the deletion is at an interior node (a position with two children), one first finds the predecessor node (in the in order sense, not in the tree structure sense). This node will always be a leaf (why?), and can be found by looking at the left child of the starting node, then going right as far as possible. This predecessor node has its data swapped with the target node, and then the predecessor leaf node is deleted. (This is not the only possible strategy.) For instance, if in the tree on the left of figure 14.6 the node with the data 4 (happens to be the root) were to be deleted, its in order predecessor (go left, then take as many rights as possible) is 3, which is swapped with the 4, and its node deleted, resulting in the structure shown in figure 14.7.
Here is the implementation of the binary tree, with the specific imports for this same data type. To change the implementation, just change the name of this module and of the ADT module being imported from (write it first) appropriately; nothing else needs to be altered.
IMPLEMENTATION MODULE CountryBinaryTree; (* semi-generic tree type by R. Sutcliffe last modified 1995 06 07 *) FROM Countries IMPORT Country, KeyType, ActionProc, Compare, GetKey, Assign, New, Valid, Dispose; FROM Storage IMPORT ALLOCATE, DEALLOCATE; FROM STextIO IMPORT WriteString, WriteLn, WriteChar; FROM Strings IMPORT CompareResults; TYPE NodePointer = POINTER TO TreeNode; TreeNode = RECORD item : DataType; leftPoint, rightPoint, parent : NodePointer; END; BinaryTree = POINTER TO TreeData; TreeData = RECORD root : NodePointer; state : TreeState; END; NodeProc = PROCEDURE (NodePointer); (* TreeState = (allRight, empty, entableFailed, notFound, bad); *) (* Here is a collection of local procs used in this module *) PROCEDURE MakeNode () : NodePointer; VAR temp : NodePointer; BEGIN NEW (temp); (* get node memory *) IF temp # NIL THEN New (temp^.item); (* node OK so get data value memory *) IF NOT Valid (temp^.item) THEN (* failed so return NIL *) DISPOSE (temp); END; END; RETURN temp; END MakeNode; PROCEDURE InsertNode (VAR root : NodePointer; newNode : NodePointer); VAR point : NodePointer; done : BOOLEAN; BEGIN IF root = NIL THEN (* first item *) root := newNode; newNode^.parent := NIL; ELSE point := root; done := FALSE; REPEAT IF Compare(GetKey(newNode^.item),GetKey (point^.item)) = greater THEN IF point^.rightPoint = NIL (* at end *) THEN point^.rightPoint := newNode; done := TRUE; ELSE point := point^.rightPoint END; (* if point *) ELSE (* less or equal *) IF point^.leftPoint = NIL (* at end *) THEN point^.leftPoint := newNode; done := TRUE; ELSE point := point^.leftPoint END; (* if point *) END; (* if compare *) UNTIL done; newNode^.parent := point; END; (* if root *) END InsertNode; PROCEDURE Find (root : NodePointer; key : KeyType; VAR point : NodePointer); (* get a pointer to the node belonging to the key. Returns NIL if not found *) BEGIN IF (root = NIL) (* recursion trapdoor *) OR (Compare (key, GetKey (root^.item)) = equal) (* got it *) THEN point := root; RETURN; END; (* if root *) Find (root^.leftPoint, key, point); (* if we get it, we don't want to look to the right at all *) IF point = NIL (* not found yet *) THEN Find (root^.rightPoint, key, point); END; (* if point *) END Find; PROCEDURE InOrderPredPoint (node: NodePointer) : NodePointer; (* Find pointer to Inorder predecessor, i.e. to the rightmost node in left subtree Pre: the node has a left child Post: a pointer to its in order predecessor leaf is returned *) VAR pred : NodePointer; BEGIN pred := node^.leftPoint; (* one left *) WHILE pred^.rightPoint # NIL DO (* go as far right as possible *) pred := pred^.rightPoint; END; (* while *) RETURN pred; END InOrderPredPoint; PROCEDURE SwapNodeVal (VAR a, b : NodePointer); VAR temp : DataType; BEGIN temp := a^.item; a^.item := b^.item; b^.item := temp END SwapNodeVal; PROCEDURE Delete (t : BinaryTree; node : NodePointer); (* delete a node *) VAR temp : NodePointer; BEGIN temp := node; IF temp^.leftPoint = NIL THEN (* empty left branch *) IF temp^.rightPoint = NIL (* I am a leaf *) THEN IF temp^.parent = NIL (* I am root too *) THEN t^.root := NIL; ELSE (* just leaf *) IF temp^.parent^.leftPoint = temp THEN temp^.parent^.leftPoint := NIL ELSE temp^.parent^.rightPoint := NIL END; END; KillNode (temp); RETURN; ELSE (* not a leaf so pull up right subtree *) node := node^.rightPoint; KillNode (temp); RETURN; END; ELSIF temp^.rightPoint = NIL THEN (* empty right branch *) node := node^.leftPoint; (* so pull up left subtree *) KillNode (temp); RETURN; ELSE (* no branch empty, find inorder predecessor *) temp := InOrderPredPoint (node); SwapNodeVal (node, temp); Delete (t, temp); (* recursively remove node swapped *) END; (* if *) END Delete; PROCEDURE KillNode (VAR node : NodePointer); (* give back all memory associated with node *) BEGIN IF node # NIL THEN Dispose (node^.item); 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 *) BEGIN IF r # NIL THEN Erase (r^.leftPoint); Erase (r^.rightPoint); KillNode (r); END; END Erase; (* end local procs *) PROCEDURE TreeStatus (t : BinaryTree) : TreeState; BEGIN IF t # NIL THEN RETURN t^.state; ELSE RETURN bad; END; END TreeStatus; PROCEDURE Create (VAR t : BinaryTree); BEGIN NEW (t); t^.root := NIL; t^.state := empty; END Create; PROCEDURE Insert (t : BinaryTree; data : DataType); VAR temp : NodePointer; state : TreeState; BEGIN state := TreeStatus (t); IF (state = bad) OR (state = entreeFailed) THEN t^.state := entreeFailed; RETURN END; temp := MakeNode (); (* status ok so get node memory *) IF temp = NIL THEN t^.state := entreeFailed; RETURN END; (* all OK so put it together *) Assign (data, temp^.item); (* move data value in *) temp^.leftPoint := NIL; (* always adding a leaf *) temp^.rightPoint := NIL; InsertNode (t^.root, temp); t^.state := allRight; END Insert; PROCEDURE Fetch (t : BinaryTree; key : KeyType; VAR data : DataType); VAR point : NodePointer; BEGIN IF t = NIL THEN t^.state := bad; RETURN ELSE Find (t^.root, key, point); IF point = NIL THEN t^.state := notFound; ELSE t^.state := allRight; data := point^.item; END; (* if point *) END; (* if t *) END Fetch; PROCEDURE Update (t : BinaryTree; data : DataType); VAR point : NodePointer; BEGIN IF t = NIL THEN t^.state := notFound; RETURN ELSE Find (t^.root, GetKey (data), point); IF point # NIL THEN t^.state := allRight; point^.item := data; END; END; END Update; PROCEDURE Remove (t : BinaryTree; key : KeyType; VAR data : DataType); VAR point : NodePointer; BEGIN IF t = NIL THEN t^.state := bad; RETURN ELSE Find (t^.root, key, point); IF point = NIL THEN t^.state := notFound; ELSE t^.state := allRight; data := point^.item; Delete (t, point); END; END; END Remove; PROCEDURE Destroy (VAR t : BinaryTree); BEGIN Erase (t^.root); (* all nodes *) DISPOSE (t); (* tree data *) END Destroy; (* local procs: three ways to traverse a sub-tree *) PROCEDURE InTraverse (r : NodePointer; Proc : ActionProc); BEGIN IF r = NIL (* recursion trap door *) THEN RETURN END; InTraverse (r^.leftPoint, Proc); Proc (r^.item); InTraverse (r^.rightPoint, Proc); END InTraverse; PROCEDURE PreTraverse (r : NodePointer; Proc : ActionProc); BEGIN IF r = NIL (* recursion trap door *) THEN RETURN END; Proc (r^.item); PreTraverse (r^.leftPoint, Proc); PreTraverse (r^.rightPoint, Proc); END PreTraverse; PROCEDURE PostTraverse (r : NodePointer; Proc : ActionProc); BEGIN IF r = NIL (* recursion trap door *) THEN RETURN END; PostTraverse (r^.leftPoint, Proc); PostTraverse (r^.rightPoint, Proc); Proc (r^.item); END PostTraverse; (* end local procs *) PROCEDURE Traverse (t : BinaryTree; Proc : ActionProc; order : TraverseOrder); BEGIN IF t = NIL THEN RETURN END; CASE order OF in: InTraverse (t^.root, Proc) | pre: PreTraverse (t^.root, Proc) | post: PostTraverse (t^.root, Proc) END; END Traverse; END CountryBinaryTree.
As before, a simple test harness is provided. In order to ensure that all aspects of the library were tested, it contains (in, pre, and post) procedures to traverse the tree and to write out enough of the data from the items being entreed to ensure that the structure is correctly maintained.
This module is in the style favoured by some that employs only unqualified import. As can readily be seen, such a style tends to become cumbersome as the module names grow.
MODULE TestCountryBinaryTree; (* program to test the logic of the Tree library with countries and their gnp by R. Sutcliffe modified 1995 06 01 *) IMPORT Countries, CountryBinaryTree, STextIO, SWholeIO; VAR Tree : CountryBinaryTree.BinaryTree; country, fetched : Countries.Country; str : Countries.KeyType; num : Countries.FieldType; gotIt : BOOLEAN; PROCEDURE WriteCountryName (c : Countries.Country); BEGIN STextIO.WriteString (Countries.GetKey (c)); STextIO.WriteChar (" "); END WriteCountryName; PROCEDURE WriteTree; (* all data *) BEGIN CountryBinaryTree.Traverse (Tree, Countries.WriteCountryData, CountryBinaryTree.in); END WriteTree; (* these three just write the names *) PROCEDURE WriteTreeIn; BEGIN CountryBinaryTree.Traverse (Tree, WriteCountryName, CountryBinaryTree.in); END WriteTreeIn; PROCEDURE WriteTreePre; BEGIN CountryBinaryTree.Traverse (Tree, WriteCountryName, CountryBinaryTree.pre); END WriteTreePre; PROCEDURE WriteTreePost; BEGIN CountryBinaryTree.Traverse (Tree, WriteCountryName, CountryBinaryTree.post); END WriteTreePost; PROCEDURE WriteTreeAll; BEGIN STextIO.WriteString ("InOrder : ");WriteTreeIn; STextIO.WriteLn; STextIO.WriteString ("PreOrder : ");WriteTreePre; STextIO.WriteLn; STextIO.WriteString ("PostOrder: ");WriteTreePost; STextIO.WriteLn; END WriteTreeAll; PROCEDURE TestFetch (name : Countries.KeyType); BEGIN CountryBinaryTree.Fetch (Tree, name, fetched); gotIt := (CountryBinaryTree.TreeStatus (Tree) # CountryBinaryTree.notFound); IF gotIt THEN str := Countries.GetKey (fetched); STextIO.WriteString ("Got "); STextIO.WriteString (str); ELSE STextIO.WriteString ("no got "); STextIO.WriteString (name); END; STextIO.WriteLn; STextIO.WriteLn; END TestFetch; BEGIN Countries.New (country); (* do only once *) CountryBinaryTree.Create (Tree); (* test Fetch; should fail *) CountryBinaryTree.Fetch (Tree, "Xanadu", fetched); gotIt := (CountryBinaryTree.TreeStatus (Tree) # CountryBinaryTree.notFound); IF gotIt THEN str := Countries.GetKey (fetched); STextIO.WriteString ("Got "); STextIO.WriteString (str); ELSE STextIO.WriteString ("no got Xanadu"); END; STextIO.WriteLn; STextIO.WriteLn; (* now get the Tree filled up *) Countries.SetKey (country, "Samovia"); Countries.SetField (country, 13000000); CountryBinaryTree.Insert (Tree, country); Countries.SetKey (country, "Xanadu"); Countries.SetField (country, 3000); CountryBinaryTree.Insert (Tree, country); Countries.SetKey (country, "Lundy"); Countries.SetField (country, 42000); CountryBinaryTree.Insert (Tree, country); Countries.SetKey (country, "Pompey"); Countries.SetField (country, 13000); CountryBinaryTree.Insert (Tree, country); Countries.SetKey (country, "Alberta"); Countries.SetField (country, 43000); CountryBinaryTree.Insert (Tree, country); Countries.SetKey (country, "Yesterday"); Countries.SetField (country, 11000); CountryBinaryTree.Insert (Tree, country); Countries.SetKey (country, "Yahk"); Countries.SetField (country, 3000); CountryBinaryTree.Insert (Tree, country); Countries.SetKey (country, "Toronto"); Countries.SetField (country, 0); CountryBinaryTree.Insert (Tree, country); WriteTreeAll; (* test Fetchs *) TestFetch ("Xanadu"); (* should be ok *) TestFetch ("Pompey"); (* should be ok *) TestFetch ("Canada"); (* should Not be ok *) TestFetch ("Toronto"); (* should be ok *) (* test update *) STextIO.WriteString ("Before Update"); STextIO.WriteLn; WriteTree; STextIO.WriteLn; Countries.SetField (country, 10); (* should still be on Toronto *) CountryBinaryTree.Update (Tree, country); STextIO.WriteString ("After Update"); STextIO.WriteLn; WriteTree; STextIO.WriteLn; (* test removes *) CountryBinaryTree.Remove (Tree, "Pompey", fetched); IF CountryBinaryTree.TreeStatus (Tree) = CountryBinaryTree.allRight THEN STextIO.WriteString ("removed Pompey"); ELSE STextIO.WriteString ("could not remove Pompey"); END; STextIO.WriteLn; STextIO.WriteLn; (* now check to ensure its really gone *) TestFetch ("Pompey"); (* should Not be ok *) STextIO.WriteString ("after Pompey removal:"); STextIO.WriteLn; WriteTreeIn; STextIO.WriteLn; STextIO.WriteLn; (* now try to remove something not there *) CountryBinaryTree.Remove (Tree, "Canada", fetched); IF CountryBinaryTree.TreeStatus (Tree) = CountryBinaryTree.allRight THEN STextIO.WriteString ("removed Canada"); ELSE STextIO.WriteString ("could not remove Canada"); END; STextIO.WriteLn; STextIO.WriteLn; (* now remove one at an interior node *) CountryBinaryTree.Remove (Tree, "Xanadu", fetched); IF CountryBinaryTree.TreeStatus (Tree) = CountryBinaryTree.allRight THEN STextIO.WriteString ("removed Xanadu"); ELSE STextIO.WriteString ("could not remove Xanadu"); END; STextIO.WriteLn; STextIO.WriteLn; STextIO.WriteString ("after Xanadu removal "); STextIO.WriteLn; WriteTreeIn; STextIO.WriteLn; STextIO.WriteLn; (* now see if destroy seems to work *) CountryBinaryTree.Destroy (Tree); IF CountryBinaryTree.TreeStatus (Tree) = CountryBinaryTree.bad THEN STextIO.WriteString ("Tree deleted"); ELSE STextIO.WriteString ("could not destroy"); END; STextIO.WriteLn; STextIO.WriteLn; END TestCountryBinaryTree.
A run of the above test harness yielded the following results. The reader should check these results against the expected ones.
** Run log starts here ** no got Xanadu InOrder : Alberta Lundy Pompey Samovia Toronto Xanadu Yahk Yesterday PreOrder : Samovia Lundy Alberta Pompey Xanadu Toronto Yesterday Yahk PostOrder: Alberta Pompey Lundy Toronto Yahk Yesterday Xanadu Samovia Got Xanadu Got Pompey no got Canada Got Toronto Before Update Alberta 43000 Lundy 42000 Pompey 13000 Samovia 13000000 Toronto 0 Xanadu 3000 Yahk 3000 Yesterday 11000 After Update Alberta 43000 Lundy 42000 Pompey 13000 Samovia 13000000 Toronto 10 Xanadu 3000 Yahk 3000 Yesterday 11000 removed Pompey no got Pompey after Pompey removal: Alberta Lundy Samovia Toronto Xanadu Yahk Yesterday could not remove Canada removed Xanadu after Xanadu removal Alberta Lundy Samovia Toronto Yahk Yesterday Tree deleted