Linked Lists Notes
Linked Lists Notes
Contents
Introduction
Java Types
Test Yourself #1
Intro to Linked Lists
Test Yourself #2
Linked List Operations
Adding a node
Test Yourself #3
Removing a node
Using a header node
The LinkedList Class
add (to end of list)
Test Yourself #4
add (at a given position)
Test Yourself #5
The LinkedList constructor
Test Yourself #6
Comparison: Lists via Arrays versus via Linked Lists
Test Yourself #7
Linked List Variations
Doubly linked lists
Circular linked lists
Test Yourself #8
Test Yourself #9
Comparisons
Introduction
The previous set of notes discussed how to implement the ListADT interface using an array to store the items in
the list. Here we discuss how to implement the ListADT interface using a linked list to store the items.
However, before talking about linked lists, we will review the difference between primitive and non-primitive
types in Java.
Java Types
Java has two "categories" of types:
1. primitive types: short, int, long, float, double, boolean, char, and byte
2. reference types: arrays and classes
When you declare a variable with a primitive type, you get enough space to hold a value of that type. Here's
some code involving a primitive type and the corresponding conceptual picture:
http://pages.cs.wisc.edu/~skrentny/cs367-common/readings/Linked-Lists/index.html 1/17
5/12/2014 Implementing Lists Using Linked-Lists
When you declare a variable with a reference type, you get space for a reference (or pointer) to that type, not
for the type itself. You must use "new" to get space for the type itself. This is illustrated below.
Remember that class objects are also reference types. For example, if you declare a variable of type List, you
only get space for a pointer to a list; no actual list exists until you use "new". This is illustrated below,
assuming the array implementation of lists and assuming that the ArrauListconstructor initializes the items
array to be of size 3. Note that because it is an array of Objects, each array element is (automatically)
initialized to null (shown using a diagonal line in the picture).
An important consequence of the fact that non-primitive types are really pointers is that assigning from one
variable to another can cause aliasing (two different names refer to the same object). For example:
http://pages.cs.wisc.edu/~skrentny/cs367-common/readings/Linked-Lists/index.html 2/17
5/12/2014 Implementing Lists Using Linked-Lists
Note that in this example, the assignment to b[1]changed not only that value, but also the value in a[1]
(because aand bwere pointing to the same array)! However, an assignment to bitself (not to an element of
the array pointed to by b) has no effect on a:
A similar situation arises when a non-primitive value is passed as an argument to a method. For example,
consider the following 4 statements and the definition of method changeArray:
The picture below illustrates what happens when this code executes.
Note that the method call causes Aand Xto be aliases (they both point to the same array). Therefore, the
assignment X[1] = 10changes both X[1]and A[1]. However, the assignment X = new int[2]
only changes X, not A, so when the call to changeArrayfinishes, the value of A[1]is still 10, not 0.
TEST YOURSELF #1
For each line of the code shown below, draw the corresponding conceptual picture.
solution
Note that a linked list consists of one or more nodes. Each node contains some data (in this example, item 1,
item 2, etc,) and a pointer. For each node other than the last one, the pointer points to the next node in the list.
For the last node, the pointer is null (indicated in the example using a diagonal line). To implement linked lists
in Java, we will define a Listnodeclass, to be used to represent the individual nodes of the list.
// modify fields
public void setData(E d) {
data = d;
}
Note that the nextfield of a Listnode<E>is itself of type Listnode<E>. That works because in Java, every
non-primitive type is really a pointer; so a Listnode<E>object is really a pointer that is either null or points to
a piece of storage (allocated at runtime) that consists of two fields named dataand next.
To understand this better, consider writing code to create a linked list of Strings with two nodes, containing
"ant"and "bat", respectively, pointed to by a variable named l. First we need to declare variable l; here's
the declaration together with a picture showing what we have so far:
http://pages.cs.wisc.edu/~skrentny/cs367-common/readings/Linked-Lists/index.html 5/17
5/12/2014 Implementing Lists Using Linked-Lists
To make lpoint to the first node of the list, we need to use newto allocate space for that node. We want its
datafield to contain "ant"and (for now) we don't care about its nextfield, so we'll use the 1-argument
Listnodeconstructor (which sets the nextfield to null):
To add the second node to the end of the list we need to create the new node (with "bat"in its datafield and
null in its nextfield) and we need to set the nextfield of the first node to point to the new one:
TEST YOURSELF #2
Assume that the list shown above (with nodes "ant" and "bat") has been created.
Question 1: Write code to change the contents of the second node's datafield from "bat"to "cat".
Question 2: Write code to insert a new node with "rat"in its datafield between the two existing nodes.
solution
Adding a node
and that the goal is to add a new node containing newdatimmediately after n. To do this we must perform the
following steps:
http://pages.cs.wisc.edu/~skrentny/cs367-common/readings/Linked-Lists/index.html 6/17
5/12/2014 Implementing Lists Using Linked-Lists
a. make the new node's nextfield point to whatever n's nextfield was pointing to
b. make n's nextfield point to the new node.
Note that it is vital to first copy the value of n's nextfield into tmp's nextfield (step 2(a)) before setting n's
nextfield to point to the new node (step 2(b)). If we set n's nextfield first, we would lose our only pointer to
the rest of the list after node n!
Also note that, in order to follow the steps shown in the picture above, we needed to use variable tmpto create
the new node (in the picture, step 1 shows the new node just "floating" there, but that isn't possible -- we need
to have some variable point to it so that we can set its nextfield and so that we can set n's nextfield to point
to it). However, we could in fact accomplish steps 1 and 2 with a single statement that creates the new node,
fills in its dataand nextfields, and sets n's nextfield to point to the new node! Here is that amazing
statement:
n.setNext( new Listnode<String>(newdat, n.getNext()) ); // steps 1, 2(a), and 2(b)
TEST YOURSELF #3
Draw pictures like the ones given above, to illustrate what happens when node nis the last node in the list.
Does the statement
n.setNext( new Listnode<String>(newdat, n.getNext()) );
solution
Now consider the worst-case running time for this add operation. Whether we use the single statement or the
list of three statements, we are really doing the same thing:
1. Using newto allocate space for a new node (start step 1).
2. Initializing the new node's dataand nextfields (finish step 1 + step 2(a)).
3. Changing the value of n's nextfield (step 2(b)).
We will assume that storage allocation via newtakes constant time. Setting the values of the three fields also
takes constant time, so the whole operation is a constant-time (O(1)) operation. In particular, the time required
to add a new node immediately after a given node is independent of the number of nodes already in the list.
Removing a node
To remove a given node nfrom a linked list, we need to change the nextfield of the node that comes
immediately before nin the list to point to whatever n's nextfield was pointing to. Here's the conceptual
picture:
http://pages.cs.wisc.edu/~skrentny/cs367-common/readings/Linked-Lists/index.html 8/17
5/12/2014 Implementing Lists Using Linked-Lists
Note that the fact that n's nextfield is still pointing to a node in the list doesn't matter -- nhas been removed
from the list, because it cannot be reached from l. It should be clear that in order to implement the remove
operation, we first need to have a pointer to the node before node n(because that node's nextfield has to be
changed). The only way to get to that node is to start at the beginning of the list. We want to keep moving
along the list as long as the current node's nextfield is not pointing to node n. Here's the appropriate code:
Listnode<String> tmp = l;
while (tmp.getNext() != n) { // find the node before n
tmp = tmp.getNext();
}
Note that this kind of code (moving along a list until some condition holds) is very common. For example,
similar code would be used to implement a lookup operation on a linked list (an operation that determines
whether there is a node in the list that contains a given piece of data).
Note also that there is one case when the code given above will not work. When nis the very first node in the
list, the picture is like this:
In this case, the test (tmp.getNext() != n)will always be false and eventually we will "fall off the
end" of the list (i.e., tmpwill become null and we will get a runtime error when we try to dereference a null
pointer). We will take care of that case in a minute; first, assuming that nis not the first node in the list, here's
the code that removes nfrom the list:
Listnode<String> tmp = l;
while (tmp.getNext() != n) { // find the node before n
tmp = tmp.getNext();
}
tmp.setNext( n.getNext() ); // remove n from the linked list
http://pages.cs.wisc.edu/~skrentny/cs367-common/readings/Linked-Lists/index.html 9/17
5/12/2014 Implementing Lists Using Linked-Lists
How can we test whether nis the first node in the list and what should we do in that case? If nis the first node,
then lwill be pointing to it, so we can test whether l == n. The following before and after pictures illustrate
removing node nwhen it is the first node in the list:
Here's the complete code for removing node nfrom a linked list, including the special case when nis the first
node in the list:
if (l == n) {
// special case: n is the first node in the list
l = n.getNext();
} else {
// general case: find the node before n, then "unlink" n
Listnode<String> tmp = l;
while (tmp.getNext() != n) { // find the node before n
tmp = tmp.getNext();
}
tmp.setNext(n.getNext());
}
What is the worst-case running time for this remove operation? If node nis the first node in the list, then we
simply change one field (l's nextfield). However, in the general case, we must traverse the list to find the
node before n, and in the worst case (when nis the last node in the list), this requires time proportional to the
number of nodes in the list. Once the node before nis found, the remove operation involves just one
assignment (to the nextfield of that node), which takes constant time. So the worst-case time running time for
this operation on a list with N nodes is O(N).
http://pages.cs.wisc.edu/~skrentny/cs367-common/readings/Linked-Lists/index.html 10/17
5/12/2014 Implementing Lists Using Linked-Lists
Note that if your linked lists do include a header node, there is no need for the special case code given above
for the remove operation; node ncan never be the first node in the list, so there is no need to check for that
case. Similarly, having a header node can simplify the code that adds a node before a given node n.
Note that if you do decide to use a header node, you must remember to initialize an empty list to contain one
(dummy) node, you must remember not to include the header node in the count of "real" nodes in the list (e.g.,
if you implement a size operation), and you must remember to ignore the header node in operations like
contains.
The LinkedListClass
Now let's consider how to implement our ListADTinterface using linked list instead of arrays; i.e., how to
implement a scaled-down LinkedListclass. Remember, we only want to change the implementation (the
"internal" part of the List abstract data type), not the interface (the "external" part of the abstract data type).
That means that the signatures of the public methods for the LinkedListclass will be the same as the ones for
the SimpleArrayListclass (and the ListADTinterface) and the descriptions of what those methods do will
also be the same. The only things that will change are how the list is represented and how the methods are
implemented.
Look back at the definition of the SimpleArrayListclass in the second set of notes and think about which
fields and/or methods need to be changed before reading any further.
Clearly, the type of the itemsfield needs to change, since the items will no longer be stored in an array;
instead, the items will be stored in a linked list. Since having a header node simplifies the addand remove
operations, we will assume that the linked list has a header node. Here's new declaration for the itemsfield:
private Listnode<E> items; // pointer to the header node of the list of items
Given this declaration for the itemsfield, let's think again about the three List methods that were discussed
assuming the array implementation:
http://pages.cs.wisc.edu/~skrentny/cs367-common/readings/Linked-Lists/index.html 11/17
5/12/2014 Implementing Lists Using Linked-Lists
The disadvantage of this approach is that it requires O(N) time to add a node to the end of a list with N items.
An alternative is to add a lastNodefield (often called a tail pointer) to the LinkedListclass and to
implement the methods that modify the linked list so that lastNodealways points to the last node in the linked
list (which will be the header node if the list is empty). There is more opportunity for error (since several
methods will need to ensure that the lastNodefield is kept up to date), but the use of the lastNodefield will
mean that the worst-case running time for this version of addis always O(1) and that will be important for
applications that frequently add to the end of a list (which is often a common operation).
Here's a picture of the "ant, bat, cat" list, when the implementation includes a lastNodepointer:
TEST YOURSELF #4
Write the "add at the end" method (assuming that the LinkedListclass includes both a header node and a
lastNodefield).
solution
TEST YOURSELF #5
Write the "add at a given position" method (assuming that the LinkedListclass includes both a header node
and a lastNodefield).
solution
The LinkedListconstructor
The LinkedListconstructor needs to initialize the three fields:
so that the list is empty. An empty list is one that has just a header node, pointed to by both itemsand
http://pages.cs.wisc.edu/~skrentny/cs367-common/readings/Linked-Lists/index.html 12/17
5/12/2014 Implementing Lists Using Linked-Lists
TEST YOURSELF #6
solution
space requirements
time requirements
ease of implementation
In the linked-list implementation, one pointer must be stored for every item in the list, while the array
stores only the items themselves.
On the other hand, the space used for a linked list is always proportional to the number of items in the
list. This is not necessarily true for the array implementation as described: if a lot of items are added to a
list and then removed, the size of the array can be arbitrarily greater than the number of items in the list.
However, we could fix this problem by modifying the removeoperation to shrink the array when it
becomes too empty.
In terms of time:
Adding an item to the end of a list is O(1) for the array implementation if we can use a "shadow" array
(as discussed in class) to avoid the O(N) cost of calling method expandArray. It is also O(1) for the
linked-list implementation as long as we have a lastNodefield.
Adding an item at a given position requires O(N) worst-case time for the array implementation, because
existing items need to be moved. The operation is also O(N) in the worst case for the linked-list
implementation, because we have to find the node currently in that position. So this operation is worst-
case O(N) for both implementations. However, it is worth noting that for the array implementation
adding closer to the beginning of the list is worst and adding toward the end of the list is best, while it is
the other way around for the linked-list implementation.
The getoperation is O(1) for the array implementation and worst-case O(N) for the linked-list
implementation.
In terms of ease of implementation, straightforward implementations of both the array and linked-list versions
seem reasonably easy. However, the methods for the linked-list version seem to require more special cases.
TEST YOURSELF #7
Assume that lists are implemented using linked lists with header nodes and pointers to the last node in the list.
How much time is required (using Big-O notation) to remove the first item from a list? to remove the last item
from a list? How do these times compare to the times required for the same operations when the list is
implemented using an array?
http://pages.cs.wisc.edu/~skrentny/cs367-common/readings/Linked-Lists/index.html 13/17
5/12/2014 Implementing Lists Using Linked-Lists
solution
Recall that, given (only) a pointer to a node nin a linked list with N nodes, removing node ntakes time O(N)
in the worst case, because it is necessary to traverse the list looking for the node just before n. One way to fix
this problem is to require two pointers: a pointer to the node to be removed and also a pointer to the node just
before that one.
Another way to fix the problem is to use a doubly linked list. Here's the conceptual picture:
Each node in a doubly linked list contains three fields: the data and two pointers. One pointer points to the
previous node in the lis, and the other pointer points to the next node in the list. The previous pointer of the
first node and the next pointer of the last node are both null. Here's the Java class definition for a doubly linked
list node:
public DblListnode(E d) {
this(null, d, null);
}
// modify fields
public void setData(E d) {
data = d;
}
To remove a given node nfrom a doubly linked list, we need to change the prevfield of the node to its right
and we need to change the nextfield of the node to its left, as illustrated below.
http://pages.cs.wisc.edu/~skrentny/cs367-common/readings/Linked-Lists/index.html 15/17
5/12/2014 Implementing Lists Using Linked-Lists
Unfortunately, this code doesn't work (causes an attempt to dereference a null pointer) if nis either the first or
the last node in the list. We can add code to test for these special cases, or we can use a circular, doubly
linked list, as discussed below.
The class definitions are the same as for the non-circular versions. The difference is that, instead of being null,
the nextfield of the last node points to the first node and (for doubly linked circular lists) the prevfield of the
first node points to the last node.
The code given above for removing node nfrom a doubly linked list will work correctly except when node n
is the first node in the list. In that case, the variable lthat points to the first node in the list needs to be updated,
so special-case code will always be needed unless the list includes a header node.
Another issue that you must address if you use a circular linked list is that if you're not careful, you may end
up going round and round in circles! For example, what happens if you try to search for a particular value val
using code like this:
Listnode<E> tmp = L;
while (tmp != null && !tmp.getData().equals(val)) {
tmp = tmp.getNext();
}
and the value is not in the list? You will have an infinite loop!
TEST YOURSELF #8
Write the correct code to search for value valin a circular linked list pointed to by l. (Assume that the list
contains no null data values.)
solution
With circular lists, you don't need pointers to both ends of the list; a pointer to one end suffices. With singly
http://pages.cs.wisc.edu/~skrentny/cs367-common/readings/Linked-Lists/index.html 16/17
5/12/2014 Implementing Lists Using Linked-Lists
linked circular lists, it is most convenient to use only a pointer to the last node. With the structure it is easy to
write code that does each of the following three operations in O(1) (constant) time: addFirst, addLast,
removeFirst
TEST YOURSELF #9
Write the code for these operations. Assume you're adding these operations to a class named
CircularLinkedList, which has a lastreference to the last node in the circular singly linked list.
solution
The major advantage of doubly linked lists is that they make some operations (like the removal of a given
node, or a right-to-left traversal of the list) more efficient.
The major advantage of circular lists (over non-circular lists) is that they eliminate some special-case code for
some operations. Also, some applications lead naturally to circular list representations. For example, a
computer network might best be modeled using a circular list.
http://pages.cs.wisc.edu/~skrentny/cs367-common/readings/Linked-Lists/index.html 17/17