Notes for July 1, 2005

Back Pointers

Below is the method we have been using to add a value to a linked list:
    public void add(int value) {
        if (front == null) {
            front = new ListNode(value);
        } else {
            ListNode current = front;
            while (current.next != null) {
                current = current.next;
            }
            current.next = new ListNode(value);
        }
    }
This is very inefficient. To add something, we have to repeatedly traverse the whole list until we reach the last node. Wouldn't it be better if we just kept a pointer to the last node at all times?

The new class declaration (with changes highlighted) will be:

    public class LinkedIntList {
        private ListNode front;
        private ListNode back;

        <methods>
    }
The constructor would have to initialize the back pointer:
    public LinkedIntList() {
        front = null;
        back = null;
    }
And now the add method can be made much more efficient at the cost of a single pointer!
    public void add(int value) {
        if (front == null) {
            front = new ListNode(value);
            back = front;
        } else {
            back.next = new ListNode(value);
            back = back.next;
        }
    }
The remove method does not experience any increases in efficiency (ask yourself why), but care must be taken to make sure that the back pointer is updated properly when the node to be removed is being pointed to by back.
    public void remove(int index) {
        if (index == 0) {
            front = front.next;

	    // if front is null, then there was only one node
	    // in the list, and therefore back must have been
	    // pointing to it too.  Reset back to null.
            if (front == null) {
                back = null;
            }
        } else {
            ListNode current = front;
            for (int i = 0; i < index - 1; i++) {
               current = current.next;

	       // if current.next is the same node as that pointed
	       // to by back, then the last node is being removed,
	       // and back should be set to the prior node, namely
	       // current
               if (current.next == back) {
                   back = current;
               }
               current.next = current.next.next;
            }
        }
    }

Sentinel Nodes

What is a "sentinel"? To some, this is a sentinel. It comes from the words "sentry", "guard", and "watch" (not the ticking kind). Knowing these definitions is not important, but it might be useful to add the word to your vocabulary. In the world on computer science, the sentinel node is a dummy node at the front of a linked list. The reading refers to it as a header node. The value in the sentinel node is irrelevant and is not part of the list as seen by the client. A linked list with a sentinel node would be constructed as follows:
    public LinkedIntList() {
        front = new ListNode();
    }
Why is this at all useful? In writing the add method, we had to consider the case when front was null. Now, front can never be null! There is always at least one node there. The add method can be modified as follows:
    public void add(int value) {
        if (front == null) {
            front = new ListNode(value);
        } else {
            ListNode current = front;
            while (current.next != null) {
                current = current.next;
            }
            current.next = new ListNode(value);
        }
    }
For clarity:
    public void add(int value) {
        ListNode current = front;
        while (current.next != null) {
            current = current.next;
        }
        current.next = new ListNode(value);
    }
The addSorted method can also be modified to eliminate the initial checks of null and looking at the first node:
    public void addSorted(int value) {
        ListNode current = front;
        while (current.next != null && current.next.data < value) {
            current = current.next;
        }
        current.next = new ListNode(value, current.next);
    }

Circular Lists

Nothing says that the next pointer of a node has to point to an unpointed node. The reading has some pretty pictures of circular lists. For now, consider the case of lists (without sentinel nodes) whose last node point back to the first node. How would we print the contents of this list?

Here is the old way:

    public void print() {
        ListNode current = front;
        while (current != null) {
            System.out.print(current.data + " ");
            current = current.next;
        }
    }
What is wrong with this code? The while is supposed to stop when current == null, however, unless the list is empty, current will NEVER be null! It will loop forever and ever and ever.

Here's the fix:

    public void print() {
        ListNode current = front;
        while (current != null) {
            System.out.print(current.data + " ");
            current = current.next;
	    if (current == front) {
                break;
            }
        }
    }
Note that that isn't the only solution. Here is another way:
    public void print() {
        if (front != null) {
	    System.out.print(front.data + " ");
	    ListNode current = front.next;
	    while (current != front) {
	        System.out.print(current.data + " ");
                current = current.next;
	    }
        }
    }
When testing programs, always check that it works for all cases. In particular,

Doubly-linked Lists

Refer to reading and handout #10. We'll cover these in more depth later in the quarter.