CSE143 Notes 4/12/06 (corrected 4/14/06)

Implementing Lists with Linked Lists (1)

We spent several lectures earlier in the quarter developing a class to represent a list of Strings using an array as the underlying data structure. The basic operations included the following:

We'd now like to explore reimplementing this class using a linked list instead of an array to store the data. This is a good example of data abstaction. From the client's view, the new implementation is exactly the same (presents the same interface) as the previous array-based one. But, although the new class behaves like the old one logically, the implementation is different and various operations will have different performance and behavior. One of the few external differences is that we will omit the constructor that specifies the list capacity, since we can always add nodes to a linked list (at least until we run out of memory).

For this example we'll use the following data structure for the links. This is almost the same as before, except it is specialized to string values, so we can avoid details of generic types, casting, etc.

  public class StringLink {      // a single node in a linked list of strings
    public String     item;      // string referenced by this link
    public StringLink next;      // next node in the list or null if no next node

    // construct a new StringLink with given item and next fields
    public StringLink(String item, StringLink next) {
      this.item = item;
      this.next = next;
    }
    // construct a new StringLink with the given string and a null next reference
    public StringLink(String item) {
      this(item, null);
    }
    // construct a new StringLink without initializing any fields
    public StringLink() {
      this(null, null);
    }
  }

Now we want to look at implementing a LinkedStringList. As before, this is a class that contains both methods to implement the list operations and state to represent the list. For now, let's start with a single instance variable that is either null if the list is empty or else it refers to the first link in the list.

  /**  A list of Strings (implemented with a linked list) */
  public class LinkedStringList {
    // state
    private StringLink first;   // reference to the first link in the list,
                                // or null if the list is empty
    ...
  }

Now let's look at a couple of the operations. First the constructor. As with all constructors, the job here is to make sure that any newly created LinkedStringList is properly initialized - in this case to a new, empty list. What should the code be?

  /** Construct a new, empty LinkedStringList */
  public LinkedStringList() {

  
    
  }

Now, let's look at one of the basic operations, add(s), which is supposed to add string s to the end of the list. There are some special cases to consider, but let's ignore those for now and think about the "typical" case: the list already contains some strings and we are adding a new one to the end. First, draw a picture of what this looks like before the new string is added, then sketch what needs to change to add it. (Make up some data - anything you like)

 

 

 

Now, what do we need to do? We need to create a new link for the new string, locate the last link in the existing list, and update that link's next field to point to the new node. Let's sketch out the code:

   StringLink newLink = new StringLink(s, null);


   // advance to the end of the list

   StringLink p = ________________________;
   
   while ( _________________________________________ ) {
   
   
   
   }
   
   // add newLink to the end of the list
   
   
   

Now the edge cases: this works fine provided that the list is not empty. If it is, then head is null, and references to fields of the "first" node in the list (head.next or head.item) will produce NullPointerExceptions. So we need to treat adding the first node to an empty list as a special case. With all of that taken into account, we can write the code for add.