Return to lecture notes index
February 14, 2003 (Lecture 13)

Overview

Last class we talked about how to build a linked list. Today, we are going to look at how to remove from them, and applications of these methods as it relates to other data structures. But first, we covered two remove methods, namely, removeHead() and removeTail().

removeHead()

Suppose we've got a linked list, and we want to remove the first item from this list. What do we do? How are we going to do this?


     head = head.getNext();
  

Is this all we need? Well, let's think- what if head is null? You'll get a nullPointerException, since you can't call .getNext() from a null reference. What other special cases do we need to look at? Well, what if there's only one item? The tail will equal the head, and after removal both head and tail should be null.


     if(null == head)
          return;

     if(head == tail)
          tail = null;

     head = head.getNext();
  

removeTail()

So now what do we do if we want to remove from the tail?

     tail = null;
  

Does this work? No, because need to reset tail as well as the second to the last element. But in order to do that, we have to find the second to the last element, set tail equal to it, and set its next to null. How do we get to the second to the last element? Let's try a for loop:

     for(Node index = head; index != tail; index = index.getNext());
     index.setNext(null);
     tail = index;
  

What's wrong with this code? First of all, this gets us to the last element, and we want to be at the second to the last. So the middle statement should read, index.getNext() != tail; so that it will stop with the index referencing the second to the last node. Supposing that we had index.getNext() != tail; in place, would the code work then? Still no. Why? Because Node index was declared in the for loop, making its scope within that loop only, so once you're done traversing the linked list, index no longer exists anymore. So that needs to be fixed as well. Additionally, you need to take into consideration the special cases (empty list, one item list). So after all our fixes, this is what the correct code will look like:

     if(null == head)
        return;

     if(head == tail){
        head = tail = null;
        return;    // so that you don't try to do the rest of the code after removing
                   // the sole item in the list
     }

     Node index;

     for(index = head; index.getNext() != tail; index = index.getNext());
        index.setNext(null);

     tail = index;

  

Queues

So now that we've figured out how to remove from both ends of a linked list, let's think about how we can use this knowledge. This is where Greg had students come down to the front of the class to get in a line, and explain the following example: Suppose you go to a nearby coffee shop, and you get into a line.

What do they call a line in Britain? A queue. In programming, a queue, like any line, is a first in first out structure.You enter in the back of the queue, and you leave through the front. To get in line, we "enqueue" ourselves, and to get out of line, we "dequeue".

How would we implement this with a linked list?

Both are first in, first out implementations, but in a linked list, deleting from the head is easier than deleting from the tail, since you have to traverse the entire list. Adding is pretty easy on both ends, so you should add to the tail and remove from the head to avoid being inefficient.

     class Queue {

          private LinkedList queue;
          public Queue(){
               queue = new LinkedList();
          }

          public void enqueue(Object o){
               queue.addLast(o);
          }

          public Object dequeue(){
               
              // what if list is empty?
              try{
               // Object temp = queue.getFirst(); // or queue.get(0);
               return queue.removeFirst();
               // return temp;

              }catch(Exception e){
               return null;
              }
          }    

          // you want dequeue to return an object because chances are, you 
          // will actually need
          // to look at what you've removed (ex. cashier wants to know the 
          // person leaving the line's order.
     }

  

The above class was created was using composition. Let's try doing the same using inheritance:

   class Queue extends LinkedList{

      public void enqueue(Object o){
              addLast(o);
      }

      public Object dequeue(){
             //what if list is empty?
              try{
                 return removeFirst();
              }catch(Exception e){

              return null;
              }
          }
   }
  

We don't want to use inheritance here. Why? Because you have ALL the linked list methods, so that you could "cut" in line. If someone were to do that, and write a "fake" queue implementation and hand it off to some other programmer, the attributes of the queue wouldn't make sense anymore. So building the class by composition means you will only be able to call the methods that are relevant to the Queue class, i.e. only adding from one end and removing from the other, as opposed to adding and removing from anywhere in the list.

As a matter of principal, separate from what we mentioned above, using inheritance to build the Queue class is unforgivable, because you are saying a queue is a linked list, which is blatantly not the case. Inheritance here says a queue is a type of linked list, and that it is necessarily a linked list, which is not true. You could implement a queue with Vectors, or with an Array, etc. The user of the queue won't care about how you are implementing the queue. They only care about enqueueing, dequeueing, and any other methods that would be appropriate for Queues.

Last words

Java's Stack extends the Vector class. We'll talk about stacks next class. But, for the moment, let me just suggest that stacks are in this respect analagous to Queues -- they allow insertion and removal only at one side. But, unfortunately, since the good folks at Sun goofed on this one, and used inheritence where it should not have been used, Java can't enforce this restriction. Any operation that is valid on a Vector is also valid on a Stack. For example, one can insert or remove from the middle of a Java Stack. Oops.