Return to the lecture notes index

Lecture 7 (May 24, 2005)

Linked Lists

A linked list is a very useful data structure. Linked lists are often a more time efficient alternative to vectors. Like all data structures, linked lists have their advantages and disadvantages.

Users often don't notice slowdowns in a program if they're distributed over time. But if the program suddenly runs more slowly than they expected (e.g., the video game characters "stop"), they get upset. When you run out of room in a vector, Java has to create another vector and copy the contents of the old vector into it. This takes time.

With a linked list, you use twice the number of references as you would in a vector (the actual data and a reference to the next node), but the extra cost is amortized over time.

While vectors are great for features such as indexing, they do have their drawbacks. When you add onto a vector that is full, the default for the vector to grow is to double its size. Because it must make a copy of itself before moving all of the data, your program can run a little slower when you have a lot of information. With a linked list, however, you can grow the list to as big or as small as you need, without allocating an unnecessary amount of memory.

What is a Linked List?

As you probably know, a list is an ordered collection of items. Like any good collection, you want to be able to add and remove items from your list, as well as traverse the list to look at each item or to look for a particular item.

A linked list is a collection of nodes. In Java, a Node is an object that the programmer creates, and contains two references: a references to an object, and a reference to the next Node object in the list.

Imagine that you've written a Person class, and that you would create a vector  to store them, but your instructor told you to store them in a linked list instead.

Here's a linked list of five Person objects.

There are five nodes in this linked list:

Node a
Node b
Node c
Node d
Node e

Each of these five nodes contains two references:

reference to a Person object reference to a Node object.

Take a look at each of the five nodes in the linked list. Look at the second reference, that is, the Node reference, in each of them. Notice what this Node reference refers to.

Node a refers to Node b.
Node b refers to Node c.
Node c refers to Node d.
Node d refers to Node e.
Node e refers to null, the Object that represents nothing.

With the exception of Node e, each node in the linked list refers to the next node. Vectors are "random access" data structures. This means that you can refer directly to each element in the vector.

v.elementAt(1);

Not so with a linked list. A linked list is a "sequential access" data structure. Each node knows about the node that comes after it, but the node knows nothing about any of the other nodes in the list.

Node e refers to null because it is the last node in the list. It's the end of the chain, the end of the line. Nothing comes after it.

The list also contains three other references to Nodes:

Node head
Node tail
Node index

Now look at head, tail, and index. These references refer to nodes in the linked list, too. Take a look and see which nodes each of them refers to.

head refers to the first node in the linked list. tail refers to the last node in the linked list. index can be used by the programmer to keep track of a particular node in the list.

Inserting a Node into the Linked List

You simply set your node reference to refer to the first node in the list. Take a look at the second reference in newNode. It refers to Node a.

But wait. We need to do something else. Now newNode, not Node a is at the beginning of the linked list. If you remember, head is supposed to refer to the first node in the linked list. So we need to make head reference newNode. Take another look at the picture. head does indeed refer to newNode, the new first node in the linked list.

How do we add a node into the end of a list? Much like you add a node to the beginning of the list.

You simply make newNode's second reference refer to nothing (null>), make the current end of the list (Node e} refer to newNode, make tail refer to newNode.

Deleting a Node from a Linked List

How would you remove a node from a linked list?

Take a careful look at this picture. Look at Node a. Its second reference now refers to Node c. References can reference only one object at a time. By resetting the reference to Node c, we lost Node b, which is as good as deleting it.

Traversing a list

Now we need to do something with the index reference. We can use it to refer to whatever node we like. If we want index to refer to the first node in the list, we can write

index = a;

If we want to access the Person object inside of Node a, we can write

Person temp = a.getData();

assuming that we have written a getData() method in the Node class. But in real linked lists, we don't actually call the individual nodes names like

Node a; Node b; Node c; Node d;

That's what special about linked lists. With the exception of the first node in the list, we refer to each node in terms of the node before it.

Look at the picture of the linked list again. index currently refers to the first node in the list, the head of the list. Without knowing that b is named b, how can I now make index refer to node b?

When we write the Node class, we will give it two instance variables, one for the reference to the object the node refers to, which in our case is a Person object, and one for the reference to the Node object that comes after it, the next node.

private Person data;
private Node next;

Let's also assume that we've written getData() that returns data for that node, and getNext() that returns next for that node.

Then if I want index to refer to b, I simply write

index = index.getNext(); //change index so that it refers to the next node

I reset the index reference to refer to the node that comes after the node index currently refers to. In this way, I can walk through the entire list if I like, stopping when I reach a node in which index.getNext(); refers to null.

Implementing A Node

Before we can create a linked list, we first need to create a class to represent one piece of the list, called a node. A node consists of an Object to store the data, and a reference to the next Node in the list.

Since a Node is only useful as a part of a linked list, we can make the implementation of the Node class part of the implementation of the LinkedList. When one class is defined inside another class, we call this a subclass. So, we can define the Node class as a subclass of the LinkedList class. This insures that no one outside of the LinkedList class can use a Node by itself.

Implementing A Linked List

Now that we have a node, how do we construct a linked list? Since each node stores a reference to the next one in the list, we at least need a reference to the beginning of the list. We call this reference the "head" of the list, and from the head we can get to any other node in the list. For convenience, we will also store a reference to the end of the list, called the "tail". This saves us the time it takes to get from the beginning to the end of the list when we need to operate on the last node.

For another level of convenience, we will create a reference called "index", which will be able to move along the nodes in the list. The index allows us to operate on a specific spot in the list without have to walk the list to get to that spot.

The "LinkedList" Class

The following gives a definition for a LinkedList class. It includes subclasses Node, which represents one piece of the list, and IndexException, which is an Exception which can be thrown if there is a problem using the index reference.


/*
 * This class defines a LinkedList.  It includes
 * subclasses Node and IndexException.  The class
 * stores a reference to the head and tail of the
 * list, and also an index reference to the current
 * position in the list.
 */
class LinkedList
{
	/*
	 * This class defines an Exception which can be
	 * thrown in the case of an error involving the
	 * "index" reference.  It is a public subclass,
	 * so that other classes can catch it, but have
	 * to explicitly recognize its scope by referring
	 * to it by LinkedList.IndexException
	 */
	public class IndexException extends Exception
	{
		/*
		 * This method creates a String representation
		 * of the Exception which has occurred
		 */
		public String toString()
		{
			return ("Bad index in Linked List");
		}
	}

	/*
	 * This class defines one piece of the linked list.
	 * It contains an Object which represents the data
	 * stored at the current spot, and a reference to
	 * the next Node in the list
	 */
	private class Node
	{
		private Object data;
		private Node next;

		/*
		 * Constructor.  Initializes instance variables
		 * to specified values
		 */
		public Node (Object data, Node next)
		{
			this.data = data;
			this.next = next;
		}

		/*
		 * Constructor.  Initializes data to the
		 * specified value, and the next reference
		 * to null
		 */
		public Node (Object data)
		{
			this.data = data;
			this.next = null;
		}

		/*
		 * Returns the data
		 */
		public Object getData()
		{
			return data;
		}

		/*
		 * Returns reference to next node
		 */
		public Node getNext()
		{
			return next;
		}

		/*
		 * Sets the next reference to the specified value
		 */
		public void setNext(Node next)
		{
			this.next = next;
		}

		/*
		 * Sets the data to the specified value
		 *
		 * We could have chosen not to implement this
		 * method, and instead force users to create
		 * new Node objects when they want to change
		 * the data.
		 */
		public void setData(Object data)
		{
			this.data = data;
		}
	}

	private Node head;
	private Node tail;
	private Node index;

	/*
	 * Constructor.  By default, set all the references
	 * to null
	 */
	public LinkedList()
	{
		head = tail = index = null;
	}

	public void addHead(Object data)
	{
		// Create new node w/data
		Node newNode;
		newNode = new Node(data);

		// Set new node to point to the old head
		newNode.setNext(head);

		// Reset head of list
		head = newNode;

		// Special case: empty list
		if (null == tail)
		{
			tail = head = index = newNode;
		}
	}

	public void addTail(Object data)
	{
		// Create new node w/data
		Node newNode = new Node(data);

		// special case: Empty list
		// can test either head or tail == null
		if (null == head)
		{
			head = tail = index = newNode;
			return; // all done
		}

		// Common case: list is not empty
		tail.setNext(newNode);

		tail = newNode;
	}

	public void resetIndex()
	{
		index = head;
	}

	public Object getIndexedNode()
	{
		// special case: null index
		if (null == index)
		{
			return null;
		}

		// Common case: return indexed value
		return index.getData();
	}

	public void advanceIndex()
	{
		// special case: null index
		if (null == index)
		{
			return;
		}

		// Common case: move forward
		index = index.getNext();
	}

	public void insertAfterIndex(Object data)
		throws IndexException
	{
		// special case: null index
		if (null == index)
		{
			throw new IndexException();
		}

		// Common case
		Node newNode = new Node(data);

		newNode.setNext(index.getNext());

		index.setNext(newNode);

		// BAD mistake:  index = newNode;
	}

	public void deleteAfterIndex()
	{
		// special case 1: null index
		if (null == index)
		{
			return;
		}

		// special case 2: null index.next
		if (null == index.getNext())
		{
			return;
		}

                // special case 3: we are removing the tail
                if (tail == index.getNext())
                {
                        index.setNext(null);
                        tail = index;
                        return;
                }

		// common case
		index.setNext(index.getNext().getNext());

		return;
	}

}