Maintaining a Large Ordered List
5.3 PROCESSING LINKED LISTS RECURSIVELY
The actual argument to a recursive linked list procedure is a pointer to the remainder of a list (initially Root_NP, the pointer to the entire list). The procedure processes the current node and then calls itself recursively to process the remainder (unless the remainder is empty). While processing the current node, the procedure may modify the dummy argument pointer. Such modifications affect the actual linkage of the list, and not merely a copy of the argument pointer.
The following example shows how to insert a new item into an ordered linked list. Arguments in the initial call to the recursive subroutine are Root_NP and the data to be inserted.
The outer if construct tests whether the current node pointer is associated (i.e., whether it has an actual target).
• If the current node pointer is not null, the inner if construct compares the Key in the current node, referred to as Arg_NP % Info % Key, with the Key component of the given item. This comparison has three possible outcomes:
1. If the keys match, information in the current node is to be modified.
2. If Arg_NP % Info % Key is larger, no matching key will be found — the search has passed the point where a match would have occurred — so the given item is inserted ahead of the given node. The procedure Insert_Target discussed earlier performs this operation and correctly modifies the current input pointer.
3. Otherwise, Arg_NP % Info % Key is smaller than the search key, so the procedure is called recursively to search the remainder of the list.
• If the current node pointer is null, the else block of the outer if construct is executed and the given item is inserted at the end of the list.
recursive subroutine Look_Up( Arg_NP, Item ) type (Node_Type), pointer :: Arg_NP
type (Info_Type), intent(in) :: Item
! start subroutine Look_Up
if (associated( Arg_NP )) then
if (Item % Key == Arg_NP % Info % Key) then call Modify_Target( Arg_NP, Item )
else if (Arg_NP % Info % Key > Item % Key) then
call Insert_Target( Arg_NP, Item ) ! Insert before next node.
else
call Look_Up( Arg_NP % Next_NP ) ! Keep looking.
end if else
call Insert_Target( Arg_NP, Item ) ! Insert at end of linked list.
end if return
end subroutine Look_Up
This recursive approach is very elegant, but the time and space overhead imposed by recursion leave some scholars unconvinced of its merits. A new activation record (see Section 3.1) is created at each recursive call — that is, for each node encountered by the linked list operation. For a very long linked list, the amount of extra storage occupied by these activation records may be intolerable. On the other hand, a long list will itself necessarily occupy a great deal of storage unless the amount of data in each node is trivial.
5.2 OPERATIONS ON WHOLE LINKED LISTS
Nevertheless, for long lists it is well to minimize the space occupied by each activation record. In particular, the procedure Look_Up has an argument Item that might be a large data structure in an actual application; it would be well to avoid storing redundant copies of this Item. A solution is to con-struct the subroutine Look_Up as a nonrecursive “wrapper.” The application program calls Look_Up, which in turn calls a recursive internal procedure R_Look_Up that inherits Item from the wrapper. The argument Item is stored only in the activation record for the wrapper; the activation record for R_Look_Up is now quite small because there are no local variables and the only argument is a pointer.
Say It with Fortran
Example 14
! Example 14. Linked List with Recursion module D14_M
implicit none
public :: Look_Up, Print_List, Delete_List integer, parameter, public :: S_LEN = 20 type, public :: Info_Type
character (len = S_LEN) :: Key
integer, dimension(2) :: Data = (/ 0, 1 /) end type Info_Type
type, private :: Node_Type type (Info_Type) :: Info
type (Node_Type), pointer :: Next_NP => Null( ) end type Node_Type
type (Node_Type), pointer, private :: Root_NP => Null( ) contains
subroutine Look_Up( Item )
type (Info_Type), intent(in out) :: 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 Modify_Target( Arg_NP, Item )
else if (Arg_NP % Info % Key > Item % Key) then
call Insert_Target( Arg_NP, Item ) ! Insert ahead of next node.
else
call R_Look_Up( Arg_NP % Next_NP ) ! Keep looking.
end if else
call Insert_Target( Arg_NP, Item ) ! Insert at end of linked list.
end if return
end subroutine R_Look_Up
111 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
subroutineInsert_Target(Arg_NP,Item) type(Node_Type),pointer::Arg_NP,Temp_NP type(Info_Type),intent(in)::Item
! startsubroutineInsert_Target allocate(Temp_NP)
Temp_NP%Info=Item
Temp_NP%Next_NP=>Arg_NP Arg_NP=>Temp_NP
return
endsubroutineInsert_Target endsubroutineLook_Up
subroutinePrint_List()
! startsubroutinePrint_List callR_Print_List(Root_NP) return
contains
subroutineR_Print_List(Arg_NP) type(Node_Type),pointer::Arg_NP
! startsubroutineR_Print_List if(associated(Trav_NP))then
callPrint_Target(Trav_NP)
callR_Print_List(Arg_NP%Next_NP) !Advancetonextnode endif
return
endsubroutineR_Print_List subroutinePrint_Target(Arg_NP)
type(Node_Type),pointer::Arg_NP
! startsubroutinePrint_Target print*,"Print:",Arg_NP%Info return
endsubroutinePrint_Target endsubroutinePrint_List subroutineDelete_List()
! startsubroutineDelete_List callR_Delete_List(Root_NP) return
contains
5.3 PROCESSING LINKED LISTS RECURSIVELY
subroutineR_Delete_List()
! startsubroutineR_Delete_List if(associated(Root_NP))then
print*,"Deleted:",Delete_Target(Root_NP) Delete at root: Next call deletes at the same point.
call R_Delete_List( ) end if
return
end subroutine R_Delete_List
function Delete_Target( Arg_NP ) result(Item) type (Node_Type), pointer :: Arg_NP, Temp type (Info_Type) :: Item
! start function Delete_Target Temp_NP => Arg_NP
Item = Arg_NP % Info
Arg_NP => Arg_NP % BOX % Next_NP deallocate( Temp_NP )
end function Delete_Target end subroutine Delete_List end module D14_M
program D14 use D14_M implicit none integer :: EoF
type (Info_Type) :: Item
! start program D14
open (1, file = "dxf.txt", status = "old", action = "read", &
position = "rewind") do
read (1, *, iostat = EoF) Temp_NP % Key if (EoF < 0) exit
Item % Data(1) = Item % Data(1) + 1 call Look_Up( Item )
end do
call Print_List( ) call Delete_List( ) stop
end program D14
As mentioned earlier, a linked list may be viewed as a recursive data structure: Each node contains data as well as a pointer to the remainder of the list. Besides its philosophical elegance, recursive linked list processing fortuitously circumvents a syntactic shortcoming of Fortran and of some other program-ming languages. The recursive technique passes the pointer argument (at each recursive call) by refer-ence: what is actually transmitted is a pointer to the argument pointer. Pointers to pointers are needed, and recursion provides this facility in a (syntactically) simple manner. This philosophical elegance and syntactic simplicity are achieved at the expense of extra space and time overhead imposed by the mecha-nism of recursion, although with some care the space overhead can be minimized. Converting the tail recursion in this program to iteration without introducing significant syntactic complication seems im-possible.
113 Johnson
Predecessor Node
Remainder of List Lincoln
Current Node Traveling Pointer
Root Pointer
Washington Predecessor Node
Traveling Pointer Root Pointer