Linked ListS Too 


Headers & Trailers
Circular Linked Lists
Doubly Linked Lists
Hybrid Lists

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.


Doubly Linked Lists

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 DListNode

Dim 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
       Set P.backNode = Q.backNode
       Set Q.backNode.nextNode = P
       Set Q.backNode  = P
  End Sub
  

 

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


Hybrid Lists

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.