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.
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
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,
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.
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.
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.
There are 6 of these in total:
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.
// // 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; } |
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
The assertion that the operations
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:
Later in the course, we develop a Java application program that provides a very basic record handling facility using only the LinkedList structures.
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
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
were to be replaced by
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.
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:
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.