Outline
- linked lists
- nodes
- linked list implementation
- invariants
- circular linked list
- doubly-linked lists
- iterators
- iterator implementation
- the ListIterator interface
- the Java foreach statement
Linked list nodes (reminder)
- each node has two data fields: an element value, and a reference
to the next node
- the element value has generic type T (or E -- it must
match the type name in the class declaration)
- note that the reference to the next node is a
pointer to the next node,
and not the next node itself
- a private class is local to the enclosing (parent) class
- private data fields in the private class are accessible to the
code in the parent class
Linked list implementation
- the linked list class only really needs one class variable: a reference
to the start of the list, conventionally known as the head
of the list
- the book also has a size class variable, which can be
useful for error checking
- my implementation also keeps
a reference to the last node in the linked list
Linked list performance
- in-class exercise: what is the run-time performance of
add(E)?
- in-class exercise: what is the run-time performance of
add(int, E)?
- in-class exercise: what is the run-time performance
of remove(),
which removes the head of the linked list?
- in-class exercise: what is the run-time performance
of remove(int),
which removes an arbitrary element?
Invariants
- There are some relations that must hold between the different class
variables
- for example, size must always be the number of nodes in
the linked list
- in a linked list of length 1, head == tail
- in a linked list of length 0, both head
and tail should be null
- tail should be the node holding the last element of the list:
a list traversal beginning at the head of the list should visit the
tail node last of all
- these relations must always hold: they never change, that is,
they are invariant (invariant is something that never changes)
Methods and Invariants
- every constructor must establish the invariants of the class
- every other method may expect that the invariants are true
when the method is called, and
- every method must guarantee that the invariants are still true
at the end of the method
Using Invariants
- invariants are useful for reasoning about the program
- some invariants can be checked by the program itself
- if an invariant is ever detected to not be true, the program
should provide enough information to track down the bug (and
should either crash, or re-establish the invariant)
- in ICS 211, if your classes have any invariants, your code should
check them at the beginning and at the end of each public method
- this may help you improve your understanding of your code, and
may also help you find bugs
- see also the linked list invariants
- if checking is slow, you may have to remove the invariant checking
(e.g., add a return statement at the beginning of the check method)
when doing performance testing
Circular Linked Lists
- by convention, the next field of the last node in
a linked list has the value null
- a different convention suggests storing the value of head
in this field
- then, tail is sufficient, and
- it is not necessary to have the head field:
- head is the same as tail.next, which is a constant-time
operation
- a complete traversal of a circular list can begin from any node, not
necessarily the head or tail node
- it is easy to (mistakenly) cause list traversals to loop forever
Doubly-Linked Lists
- the linked lists so far have the limitation that it is only
possible for code to follow references in one direction in the list,
that is, forward
- node removal requires a reference to the node before
the node to be removed
- if each node also keeps a reference to the node before it,
both these problems can be solved
- the node class is straightforward:
private class DLinkedNode<E> {
private E item;
private DLinkedNode<E> prev;
private DLinkedNode<E> next;
private DLinkedNode(E value) {
item = value;
next = null;
prev = null;
}
private DLinkedNode(E value, DLinkedNode<E> prev, DLinkedNode<E> next) {
item = value;
this.next = next;
this.prev = prev;
}
}
Doubly-Linked List add
Doubly-Linked List remove
- removing a given node (node) means updating the
node's predecessor's next field, and the
node's successor's prev field:
node.prev.next = node.next;
node.next.prev = node.prev;
- here are the special cases for a linked-list that is not circular:
if ((node == head) && (head.next == null)) {
head = null;
} else {
if (node.prev != null)
node.prev.next = node.next;
if (node.next != null)
node.next.prev = node.prev;
}
Looping over the elements of a collection
Java Iterators
- a Java iterator only provides two or three operations:
- E next(), which returns the next element, and also
advances the references
- booleans hasNext(), which returns whether there is at
least one more element
- void remove(), which removes the last element returned
by next() (this method is optional)
- using remove may invalidate any other existing (concurrent)
iterators
Iterator Example
- The sieve of Eratosthenes is relatively simple using Linked Lists
and iterators:
private static java.util.LinkedList<Integer>
filter(java.util.LinkedList<Integer> list) {
java.util.LinkedList<Integer> result = new java.util.LinkedList<Integer>();
while (list.size() > 0) {
int first = list.getFirst(); // automatic unboxing of first
result.add(first); // add at the end of the result
list.removeFirst(); // now the linked list may be empty
java.util.Iterator<Integer> iter = list.iterator();
while (iter.hasNext()) {
if (iter.next() % first == 0) {
iter.remove(); // not prime, remove the number from the list
}
}
}
return result;
}
- in-class exercise (individually): write code to use an iterator
to add the values of all the elements of a
java.util.LinkedList<Number>
Iterator Implementation
- a Java iterator may or may not be internal to the collection class
- every Java iterator must have sequential access to the elements
of the collection
- every Java iterator must have at least one variable to keep track
of where it is in the traversal, that is, which elements have not yet
been returned
- the iterator could make a copy of the entire collection, but that
is unusual
- See
LinkedListIterator.java for a
very simple iterator on linked lists.
- in-class exercise (everyone together): design the code for
the iterator() method of the LinkedList class
ListIterator
- the Java Iterator interface is very general and reasonably powerful
- sometimes it is useful to be able to move backwards and forwards,
and add or replace as well as remove elements
- the ListIterator interface adds these operations to the
basic Iterator interface
- it also keeps track of the position and can return the index of
the next or previous item
Java for/foreach
- instead of having to use the while loop to use an iterator, the
for loop has been specialized to repeatedly call the iterator
LinkedList values = ...
int sum = 0;
for (Integer value: values) {
sum = sum + value;
}
- Java creates and calls the iterator, but the iterator itself is
not visible in the code
- this can also be used with arrays:
int [] values = ...
int sum = 0;
for (int value: values) {
sum = sum + value;
}
- this is called the Java enhanced for statement
or for each statement
- the foreach statement works on any expression that has a value
that satisfies the
Iterable interface, which simply requires an iterator method:
Iterator<E> iterator(); // create and return an iterator for this collection