For maximum efficiency, traditional input and output buffer queues avoid the modulo function which, according to mathematics, requires a divide operation. Instead, a simple circular transformation is com-bined with each increment step; for example:
Rear = Rear + 1
if (Rear == MAXIMUM_SIZE) Rear = 0
Only the Front and Rear positions are actually needed for Enqueue and Dequeue operations; however, the two positions are equal both when the queue is full and when it is empty. Some common highly optimized implementations still manage to avoid calculating Current_Size at each operation, by making the maximum queue size smaller by one than the array size. A Dequeue request is refused if Front and Rear are equal; an Enqueue request is refused if the circularly transformed Rear + 1 is equal to Front.
(The circular transformation is based on array size and not on maximum queue size.)
145
Chapter 7 Trees
A tree may be defined recursively as a node with zero or more descendants, each of which is a tree. A tree with no descendants is a leaf. A node has at most one ancestor; a node that has no ancestor is a root. A tree has exactly one root. A tree has the further property that there is exactly one path from the root to each node. Because of the natural representation of a tree as a recursive structure, tree operations are most easily implemented with recursive procedures.
We have noted that arrays are relatively inflexible: the size cannot be changed after data is stored, and inserting a new element in the middle requires moving all elements to the right of the insertion point. Trees, like linked lists, do not have these disadvantages, since they are constructed with pointers whose target nodes can be allocated dynamically. A tree can provide logarithmic access time to an arbi-trary node, as compared to the linear time requirement for a linked list.
The operation of deleting a particular node from a tree is often rather complicated, however. Some suggestions are given below. Weiss suggests, “... If the number of deletions is expected to be small, then a popular strategy is lazy deletion. . .”33 This means that the node is not actually deleted, but is left in the tree and marked as “inactive”; the tree is then occasionally reconstructed, deleting all inactive nodes at once.
7.1 BINARY SEARCH TREES
A binary tree is one in which each node has at most two descendants, called the left subtree and the right subtree. Either subtree, or both, may be absent. A binary search tree is a binary tree with a form that makes it especially suitable for storing and retrieving data. (It is a binary tree and it is a search tree; the name is unrelated to binary search.) Each node of a binary search tree has a search key, and presumably also some data or a pointer to data, at each node. For simplicity, it will be assumed unless stated otherwise that the keys at all nodes in the tree are distinct.
A binary search tree also has an order property at each node: the key at a given node is greater than any key in the left subtree and is smaller than any key in the right subtree.
A typical binary search tree implementation has a declared pointer whose target is the root node;
initially this pointer is null. As each data item is read, a node is allocated, with null pointers to the left and right subtrees, and the key (and other information) in the new node is generated from data just read.
The first node (the root node) becomes the target of the root pointer. More generally, a search begins by comparing the new key with the key at the root; the search continues with the left subtree if the new key is smaller, or with the right subtree if the new key is larger. The search terminates when the indicated subtree is absent; i.e., the pointer to it is null. The newly allocated node is then inserted as the new target of the null pointer. It is important to note that an unsuccessful search always terminates at a leaf — i.e, at a null pointer.
33 Data Structures and Algorithm Analysis, 104
subroutineLook_Up(Search_Key) :
callR_Look_Up(Root_NP,Search_Key) :
contains
recursivesubroutineR_Look_Up(Arg_NP) :
if(associated(Arg_NP))then
if(Search_Key<Arg_NP%Key)then R_Look_Up(Arg_NP%Left,Search_Key) else if(Search_Key>Arg_NP%Key)then
R_Look_Up(Arg_NP%Right,Search_Key) else
callInsert_Target(Arg_NP) endif
:
Note that the tree search is implemented recursively. Recursion is natural for tree structures, and it is not inordinately wasteful because the depth of recursion depends only upon the distance of the cur-rent node from the root, which is expected to be much smaller than the total number of nodes in the tree (but see Section 7.2).
The tree print operation is also implemented recursively. When the following subroutine is called with the root pointer as its actual argument, the left subtree will be printed, then the current node, and finally the right subtree. Thus the entire tree will be printed in key order:
recursivesubroutineR_Print_Tree(Arg_NP) type(Node_Type),pointer::Arg_NP
! startsubroutineR_Print_Tree if(associated(Arg_NP))then
callR_Print_Tree(Arg_NP%Left_NP)
print*,Arg_NP%Info%Data,Arg_NP%Info%Key callR_Print_Tree(Arg_NP%Right_NP)
endif return
endsubroutineR_Print_Tree
A search without insertion is possible, of course. One application is to locate the largest or smallest key in a tree by searching with an “infinitely large” or “infinitely small” search key. A search for the largest key, for example, aways goes to the right until a node with a null right pointer is encountered; the key at this node is the maximum.
Deleting a node is not difficult unless the both subtrees are present. If both are absent, the node is deleted by deallocating a pointer in the predecessor node. If one subtree is present, the pointer in the predecessor node is changed so that its target is the subtree.
Weiss describes the more difficult process of deleting a node with two subtrees:34 The strategy is to replace the key of this node with the smallest key in its right subtree, which is easily found by the method just described, and recursively delete that node. (Remember that in any tree the node with the smallest key has an empty right subtree.)
Say it with Fortran
Example 22.
The following module implements Look_Up and Print_Tree operations for a binary search tree.34 Data Structures and Algorithm Analysis, 103
147
! Example 22. Binary Search Tree module D22_M
implicit none
public :: Look_Up, Print_Tree
integer, parameter, private :: KEY_LEN = 16 type, public :: Info_Type
character(len = KEY_LEN) :: Key
integer, dimension(2) :: Data = (/ 0, 0 /) end type Info_Type
type, private :: Node_Type type(Info_Type) :: Info
type (Node_Type), pointer :: Left_NP type (Node_Type), pointer :: Right_NP end type Node_Type
type (Node_Type), pointer, private :: Root_NP = null( ) contains
subroutine Look_Up( Item )
! start subroutine Look_Up call R_Look_Up( Root_NP ) return
contains
recursive subroutine R_Look_Up( Arg_NP ) type (Node_Type), pointer :: Arg_NP
! start subroutine R_Look_Up if (associated( Arg_NP )) then
if (Item % Key < Arg_NP % Info % Key) then
call R_Look_Up Arg_NP % Left_NP ) ! Search the left subtree else if (Item % Key > Arg_NP % Info % Key) then
call R_Look_Up( Arg_NP % Right_NP ) ! Search the right subtree else
Modify the target node if a matching key is found.
call Modify_Target( Arg_NP ) end if
else
call Insert_Target( Arg_NP, Item ) end if
return
end subroutine R_Look_Up
subroutine Insert_Target( Arg_NP ) type (Node_Type), pointer :: Arg_NP
! start subroutine Insert_Target
Insertion always occurs at a leaf, so there is no need to move pointers as with a linked list.
allocate(Arg_NP) Arg_NP%Info=Item return
endsubroutineInsert_Target
7.1 BINARY SEARCH TREES
subroutineModify_Target(Arg_NP) type(Node_Type),pointer::Arg_NP
! startsubroutineModify_Target
Arg_NP%Info%Data(2)=Arg_NP%Info%Data(2)+1 return
endsubroutineModify_Target endsubroutineLook_Up
subroutinePrint_Tree()
! startsubroutinePrint_Tree callR_Print_Tree(Root_NP) return
endsubroutinePrint_Tree
recursivesubroutineR_Print_Tree(Arg_NP) type(Node_Type),pointer::Arg_NP
character(len=20)::Line
! startsubroutineR_Print_Tree if(associated(Arg_NP))then
callR_Print_Tree(Arg_NP%Left_NP) print*,Arg_NP%Info
callR_Print_Tree(Arg_NP%Right_NP) endif
return
endsubroutineR_Print_Tree endmoduleD22_M
programD22 useD22_M implicitnone
type(Info_Type)::Item integer::EoF
! startprogramD22 Item%Data(2)=1 Item%Data(1)=0
open(1,file="dxf.txt",status="old",action="read",&
position="rewind") do
Item%Data(1)=Item%Data(1)+1 read(1,*,iostat=EoF)Item%Key if(EoF<0)exit
callLook_Up(Item) enddo
callPrint_Tree() stop
endprogramD22
149