Linked ListS Too
Headers
and
Trailers
In writing insertion and deletion algorithms for ordered linked lists, special cases arise when dealing with the first and last nodes.
One approach often used to simplify the algorithms for inserting and deleting is
to take precautions to never insert before the first node or after the last node, and to never delete the first node.
A linked list is usually ordered according to the value in some field --numerically by ID number, alphabetically by
name, etc. If the range of valid values for this field can be determined, it is possible to establish dummy nodes with values outside this range. For instance, if a list of students is ordered by last name, it can be assumed that there will be no student named
AAAA or ZZZZ. A header node, containing a value smaller than any legitimate
element, can be placed at the beginning of the list. A trailer node,
containing a value larger than any possible list element, can be placed at the
end of the list. The header and trailer nodes are regular nodes of the
same type as the data nodes in the list, Instead of storing list data,
however, they serve as placeholders.
A procedure to initialize the header and trailer nodes of a linked list with appropriate values passed as parameters follows. The minValue and maxValue could be declared as constants rather than passed as parameters.
'----------------------------------------------------------------------------------------------------------------------------- ' This sub initializes a header and trailer node for the list. The list is expected to be ' empty when this procedure is called, and will ultimately be ordered with respect ' to the info field. '----------------------------------------------------------------------------------------------------------------------------- Public Sub initializeList(ByRef List As ListNodeStr, _ ByVal minValue As String, _ ByVal maxValue As String) Dim lastPtr As New ListNodeStr If List Is Nothing Then ' Set up the header node Set List = New ListNodeStr List.setInfo (minValue) ' Set up trailer node lastPtr.setInfo (maxValue) Set lastPtr.nextNode = Nothing ' Link header and trailer nodes Set List.nextNode = lastPtr End If End Sub |
With the inclusion of headers and trailers, insertion and deletion involves only one case -- the case of inserting or deleting in the middle of the list. No value for the key field can be less than that in the header node, or greater than that in the trailer node (provided that the header and trailer values are selected correctly).
A header node can also be used for a completely unrelated purpose. There may be occasions when it may be necessary to store information about the list with the list itself. For example, a program may need to frequently have access to the number of elements in the linked list. The program could keep count of the number of elements in a separate variable, or it could include the count information in the list itself, by storing the count in the Info field of a header node, and incrementing or decrementing the count as insertions and deletions occur.
Depending on the application, a header, trailer, both, or neither may be applicable.
p32 Stephens
Circular
Linked
Lists
There is a problem with linear linked lists -- given a pointer to a node somewhere in the list, all of the nodes following that pointer can be accessed, but none of the nodes preceding it can be. With a linear singly linked list structure, there must always be a pointer to the beginning of the list in order to access all of the nodes in the list.
By making the pointer in the nextNode field of the last node of the linear list point back to the first node (instead of setting it to Nothing) the list becomes circular instead of linear.
A circular list does not have a true first or last node--just a ring of elements
linked to each other.
One advantage of using a circular list with data that is ordered is that both ends can be reached using a single external pointer. With a circular linked list, the program can start at any node in the list and traverse the entire list.
The external pointer to the list can point to any node and still access all of the nodes in the list. It is convenient, but not required, to have the external pointer to a circular list point to the (logical) last node in the list. This allows easy access to both ends of the list, since the nextNode field of the last node contains a pointer to the first node.
The implementation differences of circular linked lists poses some problems for the traversal, insertion, and deletion algorithms. Because the nextNode value of the last logical node is no longer Nothing, but rather points to the first logical cell, and because the external pointer, List, now points to the last logical node rather than the first, some changes are required.
Using a circular linked list requires some modifications to the way a list is traversed. The traversal no longer ends when the pointer becomes Nothing, but rather when the external pointer is reached.
One method of traversing the list begins by setting the pointer P, to List.nextNode,
which is the first logical node in the list. It then prints until P becomes equal to
List.nextNode, which is the starting point. This algorithm works when there is only one node in the list but not when the list is empty. (WHY NOT?) That condition must be tested independently.
'----------------------------------------------------------------------------------------------------------------------------- ' Cycle through a linked list, printing the value of each node '----------------------------------------------------------------------------------------------------------------------------- Public Sub printCircularList(ByRef txtBox As TextBox, ByRef List As ListNode) Dim outputString As String Dim P As ListNode Dim nextP As ListNode 'Check for empty list If Not List Is Nothing Then Set P = List.nextNode Do outputString = outputString & P.getInfo() & vbCrLf Set P = P.nextNode Loop Until P Is List.nextNode txtBox.Text = outputString End If End Sub |
![]()
The other methods require some minor modifications as well:
'---------------------------------------------------------------------------------------------------------------------------- ' Add a new node to the beginning of a linked list '---------------------------------------------------------------------------------------------------------------------------- Public Sub pushCircularNode(ByRef List As ListNode, ByVal nodeValue As Integer) Dim newNode As New ListNode ' create new node newNode.setInfo (nodeValue) ' set value of new node If List Is Nothing Then Set List = newNode Set List.nextNode = newNode Else Set newNode.nextNode = List.nextNode ' make nextNode point to start of List Set List.nextNode = newNode ' reset List to newNode End If End Sub |
![]()
'------------------------------------------------------------------------------------------------------------------------------- ' Given a value to insert in a linked list, place it in a new node, locate the proper ' position, and insert it into the list. '------------------------------------------------------------------------------------------------------------------------------- Public Sub insertCircularNode(ByRef List As ListNode, ByVal nodeValue As Integer) Dim Found As Boolean Dim P As ListNode Dim Ptr As ListNode Dim Back As ListNode Set P = New ListNode Call P.setInfo(nodeValue) If List Is Nothing Then ' if True the node must be the first Set List = P ' insert as first element Set P.nextNode = P Else Set Back = List Set Ptr = List.nextNode Found = False Do If Ptr.getInfo() > nodeValue Then Found = True Else Set Back = Ptr Set Ptr = Ptr.nextNode End If Loop Until (Ptr Is List.nextNode) Or Found Set P.nextNode = Ptr Set Back.nextNode = P If Back Is List Then Set List = Back.nextNode End If End Sub |
![]()
'------------------------------------------------------------------------------------------------------------------------------- ' Delete the node at the beginning of a linked list '------------------------------------------------------------------------------------------------------------------------------- Public Sub removeFirstCircularNode(ByRef List As ListNode) Dim P As ListNode If Not List Is Nothing Then Set P = List.nextNode Set List.nextNode = P.nextNode Set P.nextNode = Nothing ' not essential If P Is List Then Set List = Nothing Set P = Nothing End If End Sub |
![]()
'------------------------------------------------------------------------------------------------------------------------------- ' Locate the node that contains the specified value and remove it from the list '------------------------------------------------------------------------------------------------------------------------------- Public Sub deleteCircularNode(ByRef List As ListNode, ByVal targetNode As Integer) ' Assumption: the node is in the list Dim P As ListNode Dim Ptr As ListNode Dim Back As ListNode Set Ptr = List Set Back = Nothing While Ptr.getInfo() <> targetNode Set Back = Ptr Set Ptr = Ptr.nextNode Wend If Back Is Nothing Then Set List = List.nextNode Else Set Back.nextNode = Ptr.nextNode End If Set Ptr.nextNode = Nothing ' not essential If Ptr Is List Then Set List = Nothing Set Ptr = Nothing End Sub |
Destroying a Circular Linked List
Destroying a circular linked list is more involved than destroying a normal linked list. Setting List to Nothing simply prevents the list from being accessed, but it does not make it available for garbage collection. Because the nextNode field of every node points to another node, none of the nodes will have a reference count of zero, so it will not be destroyed.
The solution is to break the circular chain of references, so that at least one cell will have a reference count of zero. For example, the following lines would destroy a circular linked list:
Set
List.nextNode = Nothing
Set List = Nothing
The first line breaks the circular chain of references. At that point, no variable points to the first node in the list (recall that List points to the last node), so the system reduces its reference count to zero and destroys it. That reduces the reference count of the third node to zero, so it is destroyed. The process continues around the list until every item has been destroyed except the last node. Setting List to Nothing reduces its reference count to zero, so the final node is destroyed.
Even when using circular linked lists, such tasks as traversing a list backwards or deleting a particular node in a list given only a pointer to that node, can be difficult. Doubly linked lists, which are lists linked in both directions, can facilitate many operations. Each node of a doubly linked list contains three basic fields:
info -- the data stored in the node
nextNode -- pointer to the following node
backNode -- pointer to the preceding node
Such a list might have the following declarations:
Public backNode As DListNode
Private info As Integer
Public nextNode As DListNodeDim List As DListNode
Notice that in a linear doubly linked list, the backNode field of the first node, as well as the nextNode field of the last node, are set to Nothing.
Operations on doubly linked lists are more complicated than those on singly linked lists because there are more pointers to keep track of. For instance, in the following diagram, to insert a new node P before a given node Q requires four pointer changes.
When inserting or deleting, the order in which pointers are changed is important. When inserting P before Q, if the pointer in Q.backNode is changed first, then the pointer to the successor of Q would be lost.
The correct order for the pointer changes is:
Set P.nextNode = Q
Set P.backNode =
Q.backNode
Set Q.backNode.nextNode = P
Set Q.backNode = P
Set P.nextNode = Q
![]()
Set P.backNode = Q.backNode
![]()
Set Q.backNode.nextNode = P
![]()
Set Q.backNode = P
![]()
| Click for animation |
The procedure for inserting an element into its proper position in a doubly linked list with a header and trailer follows:
'----------------------------------------------------------------------------------------------------------------------------- ' Insert node P into the list in the proper position. '----------------------------------------------------------------------------------------------------------------------------- Public Sub insertDoubleNode(ByRef List As DListNode, _ ByVal nodeValue As Integer) Dim Q As DListNode Dim P As New DListNode Call P.setInfo(nodeValue) Set Q = List While nodeValue > Q.getInfo( ) Set Q = Q.nextNode Wend Set
P.nextNode = Q |
The procedure for inserting an element into its proper position in a doubly linked list with no header or trailer is considerably more complex, and provides a good example of the benefits of headers and trailers:
'----------------------------------------------------------------------------------------------------------------------------- ' Insert node P into the list in the proper position. '----------------------------------------------------------------------------------------------------------------------------- Public Sub insertDoubleNode(ByRef List As DListNode, _ ByVal nodeValue As Integer) Dim Found As Boolean Dim Ptr As DListNode Dim Back As DListNode Dim P As New DListNode Call P.setInfo(nodeValue) Set Back = Nothing Set Ptr = List Found = False While (Not Ptr Is Nothing) And Not Found If Ptr.getInfo() > nodeValue Then Found = True Else Set Back = Ptr Set Ptr = Ptr.nextNode End If Wend ' Pointer change 1: Set P.nextNode = Ptr Set P.nextNode = Ptr ' Pointer change 2 (Ptr.backNode = Back): Set P.backNode = Ptr.backNode Set P.backNode = Back If Back Is Nothing Then ' if True the node must be the first Set List = P ' insert as first element Else ' Pointer change 3 (Ptr.backNode = Back): Set Ptr.backNode.nextNode = P Set Back.nextNode = P End If If Not Ptr Is Nothing Then ' Pointer change 4: Set Ptr.backNode = P Set Ptr.backNode = P End If End Sub |
Doubly linked lists allow the deletion of a given node without requiring a pointer to its predecessor. Through the backNode field, the nextNode field of the preceding node can be altered to jump over the unwanted node:
Set P.backNode.NextNode = P.nextNode
The the back pointer of the following node can be reset to point to the preceding node:
Set P.nextNode.backNode = P.backNode
The following procedure deletes the node pointed to by P from the list. Again the list has a header and a trailer (to avoid problems caused by deleting the first or last node).
The operation is shown below:
![]()
![]()
![]()
'-----------------------------------------------------------------------------------------------------------------------------
' Deletes the node pointed to by P from the list.
'-----------------------------------------------------------------------------------------------------------------------------
Private Sub deleteDoubleNode (ByRef P As DListNode)
Set P.backNode.NextNode = P.nextNode
Set P.nextNode.backNode = P.backNode
Set P = Nothing
End Sub
Destroying a Doubly Linked List
Destroying a doubly linked list is more difficult than destroying singly linked or circular lists.
One solution is to convert the list into a singly linked list by setting all of the nodes' backNode pointers to Nothing to break the circular references between each node. When List is set to Nothing all of the items are freed automatically, just as in singly linked lists.
Set
Prt = List.nextNode
While
Not Ptr Is Nothing
Set Ptr.backNode =
Nothing
Set Ptr = Ptr.nextNode
Wend
Set List = Nothing
It is possible, and sometimes necessary, to implement circular doubly linked lists with, or without, headers and/or trailers.
A doubly linked list with a header and trailer can be represented as follows:
With or without headers and trailers a doubly linked list can be circular:
The procedure below traverses a circular doubly linked list backwards, printing all the elements in the list:
'----------------------------------------------------------------------------------------------------------------------------- ' Traverses a circular doubly linked list backwards, printing all ' the elements in the list '----------------------------------------------------------------------------------------------------------------------------- Private Sub printCircularDoubleList ( ByRef List as DListNode) Dim Ptr as DListNode Set Ptr = List If Not Ptr Is Nothing Then Do Debug.Print Ptr.getInfo( ) Set Ptr = Ptr.backNode Loop Until Ptr Is List End If End Sub NOT YET TESTED |
The task would be much more complicated with a singly linked list.