6. A Linked List Package and Its Use



1. Introduction

The ListCell definition that we introduced as the basis for realising a simple linked list structure in the previous lecture, does not really provide a useful collection of operations for the creation and maintenance of linked list structures. Consider the set of basic operations on lists that were described before.

  1. A method to obtain the datum contained in the first cell (or list head)
  2. A method to test whether a given list (i.e. list handle) is empty, i.e. comprises only the null reference.
  3. A method to obtain the list formed by removing the first cell.
  4. A method to add a cell to the head of an existing list.

As well as these methods being left undefined, there are the drawbacks that the process of adding new ListCells to a given list possesses, e.g. as in the line

NamesInList = new ListCell (TextReadIn,NamesInList);

of the illustrating example. Thus,

It would be better (in the sense of less risk of errors being made), if there were an Instance method associated with NamesInList to add an item. In this case it would not be necessary to ensure that that the ListCell Object referred to on the right-hand side was the same as the ListCell Object occuring on the left-hand side. Hence, a `cleaner' realisation would be if we could write,

NamesInList.AddItem( TextReadIn )

In this lecture we describe how the ListCell class is used within a class, LinkedList in order to hide the implementation details of the list structure to a rather greater extent than the naive implementation of ListCell alone provides.



2. The Linked List Class

The description given of the basic methods required suggests the class diagram for a Linked List Object that takes the form shown in Figure 6.1.

Figure 6.1: Class Diagram for a Linked List Object.

We notice that this uses the ListCell Object that was introduced in the last lecture. There is, however, no reason why applications that use the LinkedList class that we are about to realise, should have to be aware of the ListCell class's existence (let alone how it is actually implemented). For this reason, in keeping with the principles of data hiding that have been emphasised, rather than import this class definition, the implementation of ListCell is nested (as a class) within the LinkedList class realisation.

In this way the class header, specifying the fields used, is


public class LinkedList
  {
  //******************************************************
  // Embed the ListCell Class from earlier.              *
  //******************************************************
  class ListCell
  {
  protected Object Datum;
  protected ListCell Link;
  //
  public ListCell(Object head, ListCell next_cell)
    {
    Datum = head; Link=next_cell; 
    }
  }
  //******************************************************
  // Linked List Fields                                  *
  //******************************************************
  private ListCell Head;       
  private ListCell Tail;      
  private int CellCount;         // The number of list cells in this instance.

Figure 6.2: Fields of the LinkedList Class

The ListCell referenced by the field Head will be maintained in such a way that the Object Head.Datum will be the the item held at the start of the linked list. The field Head.Link which is a reference to a ListCell will be maintained so that it and the field Tail always reference the same ListCell. The int filed, CellCount is used to record the number of ListCells in the Instance. By explicitly maintaining such a count, we have an easy mechanism for testing is an instance of LinkedList is the empty list - test if CellCount==0 - and if the Tail field indicates the empty list - test if CellCount==1. Thus run-time errors that might arise because of attemtpting to pass null references can be avoided.

The LinkedList Constructor

It is sufficient to have just a default constructor that instantiates the fields of the instance to their starting values as below:


//******************************************************
// Linked List Constructor                             *
//******************************************************
public LinkedList()
  {
  Head = null; 
  Tail = null;      
  CellCount=0;                          // Initiates the empty list.
  }

Figure 6.3: LinkedList Constructor

Note that the settings of the ListCell fields, Head and Tail, are consistent with the requirements that have been specified as invariants for these described above.

The LinkedList Instance Methods

There are 6 of these in total:

  1. public boolean IsEmpty() - returns true if this LinkedList is empty, i.e. the Head field is null.

  2. public void AddHead (Object Datum ) - `push' a new ListCell onto this LinkedList. Thus the Head field will be such that Head.Datum references the Datum Object given as a parameter, and Head.Link (and Tail) point to the rest of the list, i.e. the structure referenced by Head prior to the invocation of this method.

  3. public void RemoveHead() - remove the ListCell forming the Head of this LinkedList. Thus Head is replaced by the ListCell referenced in Tail, and the Tail ListCell replaced by the new value of Head.Link.

  4. public Object GetHead() - returns the Object Head.Datum at the head of this LinkedList. The fields of the instance are unchanged.

  5. public LinkedList GetTail() - returns (as a LinkedList) the ListCell field Tail of this LinkedList

  6. public String toString() - return this LinkedList as a String in a form suitable for output.

With the first 5 basic methods, it is possible to realise any arbitrarily complex manipulation of a linked list structure, that is ever possible. We shall give some illustrations of such manipulative processes subsequently.

Turning to the realisation of the methods in Java, the main concern that must be dealt with is to ensure that the special cases of the empty list and a list with a single cell are treated correctly.

  1. public boolean IsEmpty()

    
    //
    //  Test if this instance is the Empty list.
    //
    public boolean IsEmpty()
      {
      return (CellCount==0);       // `Safer' than testing for null reference.
      };
    

  2. public void AddHead (Object Datum )

    
    //
    //************************************************************
    //  Add a new ListCell as the first cell in this Linked List *
    //  i.e. The `new' Linked List is [Datum]::[Previous List].  *
    //************************************************************
    //
    public void AddHead (Object Datum )
      {
      if (IsEmpty())               // If it's currently empty;
        {
        Head = new ListCell(Datum,Head);   // Create the new Head.
        Tail = null;                       // The link is still `null'
        CellCount=1;                       // and this list now contains 1 cell.
        }
      else                                 // If it's not empty
        {
        Head = new ListCell(Datum,Head);   // Add the new List head Datum,
        Tail = Head.Link;                  // Reset the `Tail' of the list.
        CellCount++;                       // and this list now has 1 extra cell.
        };
      }
    

  3. public void RemoveHead()

    
    //************************************************************************
    //  Remove the ListCell at the start of this Linked List                 *
    //  i.e. if the current list is [Head]::[Tail], the `new' one is [Tail]. *
    //************************************************************************
    //
    public void RemoveHead()
      {
      if (CellCount<=1)                    // There is an argument for raising
        {                                  // an exception if CellCount=0
                                           // however, we adopt the convention
                                           // that RemoveHead() applied to
                                           // the empty list leaves the empty list.
        Head = null; 
        CellCount=0;
        }
      else
        {
        Head = Tail;        // Tail is never the null reference here.
        CellCount--;        // and this list has one fewer cell.
        if (CellCount==1)
          Tail=null;        // If there's only a single cell then its Link field
                            // must point to the empty list (i.e. null reference)
        else
          Tail = Head.Link;  // Otherwise we can update Tail without any problem.
        };
      }
    

  4. public Object GetHead()

    
    //**********************************************************************
    //  Obtain the Object in the Datum field of the ListCell at            *
    //  the start of this Linked List.                                     *
    //  This method will not change the current instantiation of this List.*
    //**********************************************************************
    //
    public Object GetHead()
      {
      if (!(CellCount==0))                // If there's anything to return
        return Head.Datum;                // then return it.
      else
        return null;                      // otherwise return a null reference.
      }
    

  5. public LinkedList GetTail()

    
    //**********************************************************************
    //  Obtain the (reference) to the Linked List for the Link field of the*
    //  ListCell at the start of this Linked List                          *
    //  This method will not change the current instantiation of this List.*
    //**********************************************************************
    //
    public LinkedList GetTail()
      {
      LinkedList temporary= new LinkedList();
      temporary.Head=Head; temporary.Tail=Tail;
      temporary.CellCount = CellCount;
      temporary.RemoveHead();
      return temporary;
      }
    

  6. public String toString()

    
    //**************************************************************
    //  Convert this Linked List into a String in which each       *
    // Datum of the component ListCells is separated by a newline. *
    //**************************************************************
    //
    public String toString()
      {
      String res = new String();
      LinkedList temporary = new LinkedList();
      temporary.Head = Head; 
      temporary.Tail = Tail; 
      temporary.CellCount=CellCount;
      while (!temporary.IsEmpty())
        {
        res = res+(temporary.GetHead()).toString()+"\n";
        temporary = temporary.GetTail();
        };
      return res;
      }     
    

    Notice that the toString() method is, in effect, identical to the method PrintOut() that was used to output the data in a ListCell at the end of Lecture 5.

The complete LinkedList realisation is shown in Figure 6.4.


//
// COMP102
// Example 9: Basic Linked List
//            Creation and Maintenance
//
// Paul E. Dunne 8/11/99
//
public class LinkedList
  {
  //******************************************************
  // Embed the ListCell Class from earlier.              *
  //******************************************************
  class ListCell
  {
  protected Object Datum;
  protected ListCell Link;
  //
  public ListCell(Object head, ListCell next_cell)
    {
    Datum = head; Link=next_cell;
    }
  }
  //******************************************************
  // Linked List Fields                                  *
  //******************************************************
  private ListCell Head;
  private ListCell Tail;
  private int CellCount;         // The number of list cells in this instance.
  //******************************************************
  // Linked List Constructor                             *
  //******************************************************
  public LinkedList()
    {
    Head = null;
    Tail = null;
    CellCount=0;                          // Initiates the empty list.
    }
  //******************************************************
  // Linked List Instance Methods                        *
  //******************************************************
  //
  //  Test if this instance is the Empty list.
  //
  public boolean IsEmpty()
    {
    return (CellCount==0);       // `Safer' than testing for null reference.
    };
  //************************************************************
  //  Add a new ListCell as the first cell in this Linked List *
  //  i.e. The `new' Linked List is [Datum]::[Previous List].  *
  //************************************************************
  //
  public void AddHead (Object Datum )
    {
    if (IsEmpty())               // If it's currently empty;
      {
      Head = new ListCell(Datum,Head);   // Create the new Head.
      Tail = null;                       // The link is still `null'
      CellCount=1;                       // and this list now contains 1 cell.
      }
    else                                 // If it's not empty    {
      Head = new ListCell(Datum,Head);   // Add the new List head Datum,
      Tail = Head.Link;                  // Reset the `Tail' of the list.
      CellCount++;                       // and this list now has 1 extra cell.
      };
    }
  //************************************************************************
  //  Remove the ListCell at the start of this Linked List                 *
  //  i.e. if the current list is [Head]::[Tail], the `new' one is [Tail]. *
  //************************************************************************
  //
  public void RemoveHead()
    {
    if (CellCount<=1)                    // There is an argument for raising
      {                                  // an exception if CellCount=0
                                         // however, we adopt the convention
                                         // that RemoveHead() applied to
                                         // the empty list leaves the empty list.
      Head = null;
      CellCount=0;
      }
    else
      {
      Head = Tail;        // Tail is never the null reference here.
      CellCount--;        // and this list has one fewer cell.
      if (CellCount==1)
        Tail=null;        // If there's only a single cell then its Link field
                          // must point to the empty list (i.e. null reference)
      else
        Tail = Head.Link;  // Otherwise we can update Tail without any problem.
      };
    }
  //**********************************************************************
  //  Obtain the Object in the Datum field of the ListCell at            *
  //  the start of this Linked List.                                     *
  //  This method will not change the current instantiation of this List.*
  //**********************************************************************
  //
  public Object GetHead()
    {
    if (!(CellCount==0))                // If there's anything to return
      return Head.Datum;                // then return it.
    else
      return null;                      // otherwise return a null reference.
    }
  //**********************************************************************
  //  Obtain the (reference) to the Linked List for the Link field of the*
  //  ListCell at the start of this Linked List                          *
  //  This method will not change the current instantiation of this List.*
  //**********************************************************************
  //
  public LinkedList GetTail()
    {
    LinkedList temporary= new LinkedList();
    temporary.Head=Head; temporary.Tail=Tail;
    temporary.CellCount = CellCount;  temporary.RemoveHead();
    return temporary;
    }
  //**************************************************************
  //  Convert this Linked List into a String in which each       *
  // Datum of the component ListCells is separated by a newline. *
  //**************************************************************
  //
  public String toString()
    {
    String res = new String();
    LinkedList temporary = new LinkedList();
    temporary.Head = Head;
    temporary.Tail = Tail;
    temporary.CellCount=CellCount;
    while (!temporary.IsEmpty())
      {
      res = res+(temporary.GetHead()).toString()+"\n";
      temporary = temporary.GetTail();
      };
    return res;
    }
  }

Figure 6.4: The LinkedList Class Realisation


3. Examples of List Processing Operations Using the LinkedList Class

The assertion that the operations

  1. testing if a list is empty;
  2. modifying a list by inserting a new listcell as its head;
  3. modifying a list by removing the listcell at its head;
  4. obtaining the Datum Object in the head of a linked list; and
  5. obtaining the linked list comprsing the the tail of a linked list.

are sufficient to implement arbitrary `realisable' list processing operations may, at first sight, appear to be an exaggeration. It is, in fact, possible formally to prove the truth of this assertion. A number of you will meet the ideas underpinning such reasoning in the second year of the degree programme. It is worth noting that this property (of employing a `minimum' basis of computational operations to realise algorithmic solutions) is not something which is merely of theoretical interest. In the specific domain of linked list structures, the set of operations above (in fact just the first three of these), provide the basis for defining, in its purest form, the programming language LISP proposed by John McCarthy over 40 years ago, (qv here and here).

We present in this section a few illustrative examples of list processing operations using only the methods provided within the LinkedList class. While these are far from being a full `proof' that these methods are sufficient in themselve, the examples ought to indicate that a number of `non-trivial' list manipulation procedures can be carried out easily.

To start with we consider the following operations:

  1. Reversing the order of the ListCell in a LinkedList instance.
  2. Concatenating two LinkedList instances to form a single LinkedList

Later in the course, we develop a Java application program that provides a very basic record handling facility using only the LinkedList structures.

3.1. List Reversal

We saw in the example presented at the end of Lecture 5, that the order in which data appear within a linked list, i.e. the Object indicated by the successive Datum fields, is the reverse of the order in which these data were inserted into the list structure. Thus, in our example, the String items read were supplied in the order

First:KILMARNOCK
Second:CELTIC
Third:MOTHERWELL
Fourth:DUNDEE
Fifth:HIBERNIAN

Figure 6.5: Order in which data given for the example of Lecture 5.

but, when the data were printed, in the process of traversing the list from the list head until the null reference marking the end of the list, these appeared in the order,

First:HIBERNIAN
Second:DUNDEE
Third:MOTHERWELL
Fourth:CELTIC
Fifth:KILMARNOCK

Figure 6.6: Order in which data given are held/output for the example of Lecture 5.

The method

public static LinkedList Reverse ( LinkedList L )

given in Figure 6.7, returns a LinkedList whose ListCell components are in the reverse order of those indicated by the method's LinkedList formal parameter, L. Thus, if in our simple example given in Lecture 5, the statement

NamesInList.PrintOut()

were to be replaced by

Reverse(NamesInListPrintOut()

Then the the data printed would appear in the order given in Figure 6.5, and not as the order in Figure 6.6.


//***********************************************************************
// Form a Linked List which is ordered in                               *
// reverse from the Linked List L.                                      *
//***********************************************************************
public static LinkedList Reverse ( LinkedList L )
  {
  LinkedList res = new LinkedList();   // For the result.
  LinkedList temp = L;
  //
  // Insert successive cells of L into the result List
  // until the last cell in L is reached.
  //
  while ( !temp.IsEmpty() )
    {
    res.AddHead(temp.GetHead());
    temp = temp.GetTail();             // Move to the remainder of L.
    };
  return res;
  }

Figure 6.7: Reversing the data order in a LinkedList

Notice that Reverse uses only methods from the LinkedList class.

3.2. Concatenating/Joining two Linked Lists into a single Linked List

Reversing a list is comparatively easy having examined the original output method that was given in Lecture 5. The process of concatenating a given LinkedList, End say, onto another LinkedList, Start say, requires a little bit more analysis.

As an example, suppose we have a LinkedList called Start whose configuration is as in Figure 6.8.

Figure 6.8: The LinkedList Start

and we wish to `append' (concatenate) the LinkedList called End, configured as in Figure 6.9, onto this.

Figure 6.9: The LinkedList End

Then the structure of the resulting LinkedList should be the LinkedList Concatenate(Start,End), shown in Figure 6.10.

Figure 6.10: The LinkedList resulting from Joining End onto Start

Examining Figure 6.10, it is clear that the desired structure can be obtained by changing the field Link of the `final' ListCell in the LinkedList Start (i.e. the one whose Datum field has the value "Frank") so that it references the Head of the LinkedList instance End. In doing this, however, we must be careful not to change the structures that are referenced by the list handles for either Start or End.

Suppose we proceed in the following stages:

  1. Define a new LinkedList Instance, temp say.
  2. Traverse the LinkedList Start `pushing' each cell of this in turn onto the instance temp.
  3. Traverse the Linked List End, again `pushing' each cell of this onto the instance temp.

Inspecting the structure that results after this process, it is certainly the case that temp will indicate a concatentation of Start and End, but it will not be the desired concatenation. It will, however, be the reverse of the ordering required. It follows that by using the mechanism just outlined and then invoking the Reverse method on the outcome, will produce a LinkedList with the required structure.

So we get the realisation shown in Figure 6.11 below:


//***********************************************************************
// Form A Linked List of the form [Start]::[End]                    *
//***********************************************************************
public static LinkedList Concatenate ( LinkedList Start,
                                       LinkedList End)
  {
  LinkedList res = new LinkedList();
  LinkedList temp = new LinkedList();
  LinkedList StartPoint = Start;
  LinkedList EndPoint = End;
  // Build the first part by stacking the Start Linked List
  while (!StartPoint.IsEmpty())
    {
    temp.AddHead(StartPoint.GetHead());
    StartPoint = StartPoint.GetTail();
    };
  // Stack the End list onto the List just built.
  while (!EndPoint.IsEmpty())
    {
    temp.AddHead(EndPoint.GetHead());
    EndPoint = EndPoint.GetTail();
    };
  res = Reverse(temp); // The List wanted is the reverse of the one just built
  return res;
  }

Figure 6.11: Concatenating 2 LinkedLists by `Stacking' and Reversing.


4. Summary

  1. The ListCell defined in Lecture 5, has a number of drawbacks which result in it not being entirely suitable as vehicle for more sophisticated structures and applications built on linked lists.
  2. By nesting the ListCell class within a basic LinkedList class, implementation details of how a single cell within a linked list structure is built can be hidden from applications.
  3. The LinkedList class introduced above provides, in the shape of the 5 basic instance methods within it, sufficient functionality to permit quite complex list processing operations to be performed.
  4. Although such considerations are outwith the exact scope of this course, the implementation of list operations in terms of the basic methods given is not always the best (in terms of time) algorithmic approach. Thus it is often the case that a realisation which is algorithmically efficient be difficult to understand, and one which is more transparent is not the most efficient solution.


    (Notes prepared and maintained by Paul E. Dunne, November 1999)