CHAPTER 2
Lists and the
Collections Framework
Chapter Objectives
The List interface
Writing an array-based implementation of List
Linked list data structures:
Singly-linked
Doubly-linked
Circular
Big-O notation and algorithm efficiency
Implementing the List interface as a linked list
The Iterator interface
Implementing Iterator for a linked list
Testing strategies
The Java Collections framework (hierarchy)
Introduction
A list is a collection of elements, each
with a position or index
Iterators facilitate sequential access to
lists
Classes ArrayList, Vector, and
LinkedList are subclasses of abstract
class AbstractList and implement the
List interface
The List Interface and ArrayList
Class
Section 2.1
List Interface and ArrayList Class
An array is an indexed structure
In an indexed structure,
elements may be accessed in any order using subscript
values
elements can be accessed in sequence using a loop that
increments the subscript
With the Java Array object, you cannot
increase or decrease its length (length is fixed)
add an element at a specified position without shifting
elements to make room
remove an element at a specified position and keep the
elements contiguous without shifting elements to fill in
the gap
List Interface and ArrayList Class
(cont.)
Java provides a List interface as part of its API java.util
Classes that implement the List interface provide the
functionality of an indexed data structure and offer many more
operations
A sample of the operations:
Obtain an element at a specified position
Replace an element at a specified position
Find a specified target value
Add an element at either end
Remove an element from either end
Insert or remove an element at any position
Traverse the list structure without managing a subscript
All classes introduced in this chapter support these operations,
but they do not support them with the same degree of efficiency
java.util.List Interface and its
Implementers
List Interface and ArrayList Class
Unlike the Array data structure, classes
that implement the List interface
cannot store primitive types
Classes must store values as objects
This requires you to wrap primitive
types, such an int and double in object
wrappers, in these cases, Integer and
Double
8
ArrayList Class
The simplest class that implements the
List interface
An improvement over an array object
Use when:
you will be adding new elements to the end
of a list
you need to access elements quickly in any
order
ArrayList Class (cont.)
To declare a List “object” whose
elements will reference String objects:
List<String> myList = new ArrayList<String>();
The initial List is empty and has a
default initial capacity of 10 elements
To add strings to the list,
myList.add("Bashful");
myList.add("Awful");
myList.add("Jumpy");
myList.add("Happy");
ArrayList Class (cont.)
Adding an element with subscript 2:
myList.add(2, "Doc");
Notice that the subscripts of "Jumpy" and
"Happy" have changed from [2],[3] to [3],[4]
ArrayList Class (cont.)
When no subscript is specified, an
element is added at the end of the list:
myList.add("Dopey");
ArrayList Class (cont.)
Removing an element:
myList.remove(1);
The strings referenced by [2] to [5]
have changed to [1] to [4]
ArrayList Class (cont.)
You may also replace an element:
myList.set(2, "Sneezy");
ArrayList Class (cont.)
You cannot access an element using a
bracket index as you can with arrays
(array[1])
Instead, you must use the get()
method:
String dwarf = myList.get(2);
The value of dwarf becomes "Sneezy"
ArrayList Class (cont.)
You can also search an ArrayList:
myList.indexOf("Sneezy");
This returns 2 while
myList.indexOf("Jumpy");
returns -1 which indicates an unsuccessful
search
Generic Collections
The statement
List<String> myList = new ArrayList<String>();
uses a language feature called generic
collections or generics
The statement creates a List of String; only
references of type String can be stored in the
list
String in this statement is called a type
parameter
The type parameter sets the data type of all
objects stored in a collection
Generic Collections
(cont.)
The general declaration for generic collection is
CollectionClassName<E> variable =
new CollectionClassName<E>();
The <E> indicates a type parameter
Adding a noncompatible type to a generic collection
will generate an error during compile time
However, primitive types will be autoboxed:
ArrayList<Integer> myList = new ArrayList<Integer>();
myList.add(new Integer(3)); // ok
myList.add(3); // also ok! 3 is automatically wrapped
in an Integer object
myList.add(new String("Hello")); // generates a type
incompatability error
Why Use Generic
Collections?
Better type-checking: catch more errors,
catch them earlier
Documents intent
Avoids the need to downcast from
Object
Specification of the ArrayList Class
Applications of ArrayList
Section 2.2
Example Application of ArrayList
ArrayList<Integer> someInts = new ArrayList<Integer>();
int[] nums = {5, 7, 2, 15};
for (int i = 0; i < nums.length; i++) {
someInts.add(nums[i]);
}
// Display the sum
int sum = 0;
for (int i = 0; i < someInts.size(); i++) {
sum += someInts.get(i);
}
System.out.println("sum is " + sum);
Example Application of ArrayList
(cont.)
ArrayList<Integer> someInts = new ArrayList<Integer>();
int[] nums = {5, 7, 2, 15};
for (int i = 0; i < nums.length; i++) {
someInts.add(nums[i]);
}
nums[i] is an int; it is
// Display the sum automatically wrapped in
int sum = 0; an Integer object
for (int i = 0; i < someInts.size(); i++) {
sum += someInts.get(i);
}
System.out.println("sum is " + sum);
Phone Directory
Application
public class DirectoryEntry {
String name;
String number;
}
Create a class for
objects stored in
the directory
Phone Directory Application (cont.)
public class DirectoryEntry {
String name;
String number;
}
private ArrayList<DirectoryEntry> theDirectory =
new ArrayList<DirectoryEntry>();
Create the directory
Phone Directory Application (cont.)
public class DirectoryEntry { Add a
DirectoryEntry
String name; object
String number;
}
private ArrayList<DirectoryEntry> theDirectory =
new ArrayList<DirectoryEntry>();
theDirectory.add(new DirectoryEntry("Jane Smith",
"555-1212"));
Phone Directory Application (cont.)
public class DirectoryEntry { indexOf searches
Method
String name; theDirectory by applying the equals
String number; method for class DirectoryEntry.
Assume DirectoryEntry's equals
} method compares name fields.
private ArrayList<DirectoryEntry> theDirectory =
new ArrayList<DirectoryEntry>();
theDirectory.add(new DirectoryEntry("Jane Smith",
"555-1212"));
int index = theDirectory.indexOf(new DirectoryEntry(aName,
""));
Phone Directory Application (cont.)
public class DirectoryEntry {
String name;
String number;
}
private ArrayList<DirectoryEntry> theDirectory =
new ArrayList<DirectoryEntry>();
theDirectory.add(new DirectoryEntry("Jane Smith", "555-1212"));
int index = theDirectory.indexOf(new DirectoryEntry(aName, ""));
if (index != -1)
dE = theDirectory.get(index);
else
dE = null;
Implementation of an ArrayList
Class
Section 2.3
Implementing an ArrayList Class
KWArrayList: a simple implementation
of ArrayList
Physical size of array indicated by data field
capacity
Number of data items indicated by the data
field size
KWArrayList Fields
import java.util.*;
/** This class implements some of the methods of the Java ArrayList class
*/
public class KWArrayList<E> {
// Data fields
/** The default initial capacity */
private static final int INITIAL_CAPACITY = 10;
/** The underlying data array */
private E[] theData;
/** The current size */
private int size = 0;
/** The current capacity */
private int capacity = 0;
}
KWArrayList Constructor
public KWArrayList () {
capacity = INITIAL_CAPACITY;
theData = (E[]) new Object[capacity];
}
This statement allocates
storage for an array of type
Object and then casts the array
object to type E[]
Although this may cause a
compiler warning, it's ok
Implementing ArrayList.add(E)
We will implement two add methods
One will append at the end of the list
The other will insert an item at a
specified position
Implementing ArrayList.add(E)
(cont.)
If size is less than capacity, then to append a new item
1. insert the new item at the position indicated by the value
of size
2. increment the value of size
3. return true to indicate successful insertion
Implementing ArrayList.add(int
index,E anEntry)
To insert into the middle of the array, the
values at the insertion point are shifted
over to make room, beginning at the end
of the array and proceeding in the
indicated order
Implementing
ArrayList.add(index,E)
public void add (int index, E anEntry) {
// check bounds
if (index < 0 || index > size) {
throw new ArrayIndexOutOfBoundsException(index);
}
// Make sure there is room
if (size >= capacity) {
reallocate();
}
// shift data
for (int i = size; i > index; i--) {
theData[i] = theData[i-1];
}
// insert item
theData[index] = anEntry;
size++;
}
set and get Methods
public E get (int index) {
if (index < 0 || index >= size) {
throw new ArrayIndexOutOfBoundsException(index);
}
return theData[index];
}
public E set (int index, E newValue) {
if (index < 0 || index >= size) {
throw new ArrayIndexOutOfBoundsException(index);
}
E oldValue = theData[index];
theData[index] = newValue;
return oldValue;
}
remove Method
When an item is removed, the items that
follow it must be moved forward to close
the gap
Begin with the item closest to the
removed element and proceed in the
indicated order
remove Method (cont.)
public E remove (int index) {
if (index < 0 || index >= size) {
throw new ArrayIndexOutOfBoundsException(index);
}
E returnValue = theData[index];
for (int i = index + 1; i < size; i++) {
theData[i-1] = theData[i];
}
size--;
return returnValue;
}
reallocate Method
Create a new array that is twice the size
of the current array and then copy the
contents of the new array
private void reallocate () {
capacity *= 2;
theData = Arrays.copyOf(theData, capacity);
}
reallocate Method (cont.)
private void reallocate () {
capacity *= 2;
theData = Arrays.copyOf(theData, capacity);
}
The reason for doubling
is to spread out the cost
of copying; we discuss
this further in the next
section
KWArrayList as a Collection of
Objects
Earlier versions of Java did not support
generics; all collections contained only
Object elements
To implement KWArrayList this way,
remove the parameter type <E> from the
class heading,
replace each reference to data type E by
Object
The underlying data array becomes
private Object[] theData;
Vector Class
The Java API java.util contains two
very similar classes, Vector and
ArrayList
New applications normally use
ArrayList rather than Vector as
ArrayList is generally more efficient
Vector class is synchronized, which
means that multiple threads can access
a Vector object without conflict
Algorithm Efficiency and
Big-O
Section 2.4
Algorithm Efficiency and
Big-O
Getting a precise measure of the
performance of an algorithm is difficult
Big-O notation expresses the
performance of an algorithm as a
function of the number of items to be
processed
This permits algorithms to be compared
for efficiency
For more than a certain number of data
items, some problems cannot be solved
by any computer
Linear Growth Rate
If processing time increases in proportion
to the number of inputs n, the algorithm
grows at a linear rate
public static int search(int[] x, int target) {
for(int i=0; i < x.length; i++) {
if (x[i]==target)
return i;
}
return -1; // target not found
}
Linear Growth Rate • If the target is not present, the for loop
will execute x.length times
• If the target is present the for loop will
execute (on average) (x.length +
1)/2 times
If processing time increases in proportion
• Therefore, the total execution time is
to the number of inputs n, the algorithm
directly proportional to x.length
• This is described as a growth rate of
grows at a linear rate order n OR
• O(n)
public static int search(int[] x, int target) {
for(int i=0; i < x.length; i++) {
if (x[i]==target)
return i;
}
return -1; // target not found
}
n x m Growth Rate
Processing time can be dependent on
two different inputs
public static boolean areDifferent(int[] x, int[] y) {
for(int i=0; i < x.length; i++) {
if (search(y, x[i]) != -1)
return false;
}
return true;
}
n x m Growth Rate
• The for loop (cont.)
will execute x.length
times
• But it will call search, which will
execute y.length times
Processing time can be dependent on
• The total execution time is
two different inputs.
proportional to (x.length *
y.length)
• The growth rate has an order of n x
m or
• O(n
public static boolean x m)
areDifferent(int[] x, int[] y) {
for(int i=0; i < x.length; i++) {
if (search(y, x[i]) != -1)
return false;
}
return true;
}
Quadratic Growth Rate
If processing time is proportional to the square of
the number of inputs n, the algorithm grows at a
quadratic rate
public static boolean areUnique(int[] x) {
for(int i=0; i < x.length; i++) {
for(int j=0; j < x.length; j++) {
if (i != j && x[i] == x[j])
return false;
}
}
return true;
}
Quadratic Growth Rate
(cont.) • The for loop with i as index will
execute x.length times
• The for loop with j as index will
If processing time is proportional
execute x.lengthto the square of
times
the number of inputs
• Then, thenumber
total algorithm grows
of times the at a
inner
quadratic rate loop will execute is (x.length) 2
• The growth rate has an order of n2
or
• O(n2)
public static boolean areUnique(int[] x) {
for(int i=0; i < x.length; i++) {
for(int j=0; j < x.length; j++) {
if (i != j && x[i] == x[j])
return false;
}
}
return true;
}
Big-O Notation
The O() in the previous examples can be thought
of as an abbreviation of "order of magnitude"
A simple way to determine the big-O notation of
an algorithm is to look at the loops and to see
whether the loops are nested
Assuming a loop body consists only of simple
statements,
a single loop is O(n)
a pair of nested loops is O(n2)
a nested pair of loops inside another is O(n3)
and so on . . .
Big-O Notation (cont.)
You must also examine the number of times a loop is executed
for(i=1; i < x.length; i *= 2) {
// Do something with x[i]
}
The loop body will execute k-1 times, with i having the
following values:
1, 2, 4, 8, 16, . . ., 2 k
until 2k is greater than x.length
Since 2k-1 = x.length < 2k and log22k is k, we know that k-1 =
log2(x.length) < k
Thus we say the loop is O(log n) (in analyzing algorithms, we
use logarithms to the base 2)
Logarithmic functions grow slowly as the number of data items
n increases
Formal Definition of Big-
O
Consider the following program structure:
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
Simple Statement
}
}
for (int i = 0; i < n; i++) {
Simple Statement 1
Simple Statement 2
Simple Statement 3
Simple Statement 4
Simple Statement 5
}
Simple Statement 6
Simple Statement 7
...
Simple Statement 30
Formal Definition of Big-
O (cont.)
Consider the following program structure:
This nested loop
for (int i = 0; i < n; i++) { executes a Simple
for (int j = 0; j < n; j++) {
Statement n2
Simple Statement
times
}
}
for (int i = 0; i < n; i++) {
Simple Statement 1
Simple Statement 2
Simple Statement 3
Simple Statement 4
Simple Statement 5
}
Simple Statement 6
Simple Statement 7
...
Simple Statement 30
Formal Definition of Big-
O (cont.)
Consider the following program structure:
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
Simple Statement
}
}
for (int i = 0; i < n; i++) {
Simple Statement 1
This loop executes
Simple Statement 2 5 Simple
Simple Statement 3 Statements n
Simple Statement 4 times (5n)
Simple Statement 5
}
Simple Statement 6
Simple Statement 7
...
Simple Statement 30
Formal Definition of Big-
O (cont.)
Consider the following program structure:
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
Simple Statement
}
}
for (int i = 0; i < n; i++) {
Simple Statement 1
Simple Statement 2
Simple Statement 3
Simple Statement 4
Simple Statement 5 Finally, 25 Simple
} Statements are
Simple Statement 6 executed
Simple Statement 7
...
Simple Statement 30
Formal Definition of Big-
O (cont.)
Consider the following program structure:
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
Simple Statement
We can conclude that the
} relationship between
} processing time and n (the
for (int i = 0; i < n; i++) { number of date items
Simple Statement 1 processed) is:
Simple Statement 2
Simple Statement 3 T(n) = n2 + 5n + 25
Simple Statement 4
Simple Statement 5
}
Simple Statement 6
Simple Statement 7
...
Simple Statement 30
Formal Definition of Big-
O (cont.)
In terms of T(n),
T(n) = O(f(n))
There exist
two constants, n0 and c, greater than zero, and
a function, f(n),
such that for all n > n0, cf(n) = T(n)
In other words, as n gets sufficiently large (larger
than n0), there is some constant c for which the
processing time will always be less than or equal
to cf(n)
cf(n) is an upper bound on performance
Formal Definition of Big-
O (cont.)
The growth rate of f(n) will be determined by
the fastest growing term, which is the one
with the largest exponent
In the example, an algorithm of
O(n2 + 5n + 25)
is more simply expressed as
O(n2)
In general, it is safe to ignore all constants
and to drop the lower-order terms when
determining the order of magnitude
Big-O Example 1
Given T(n) = n2 + 5n + 25, show that
this is O(n2)
Find constants n0 and c so that, for all n
> n0, cn2 > n2 + 5n + 25
Find the point where cn2 = n2 + 5n + 25
Let n = n0, and solve for c
c = 1 + 5/ n0, + 25/ n0 2
When n0 is 5(1 + 5/5 + 25/25), c is 3
So, 3n2 > n2 + 5n + 25 for all n > 5
Other values of n0 and c also work
Big-O Example 1 (cont.)
Big-O Example 2
Consider the following loop
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
3 simple statements
}
}
T(n) = 3(n – 1) + 3 (n – 2) + … + 3
Factoring out the 3,
3(n – 1 + n – 2 + n – 3 + … + 1)
1 + 2 + … + n – 1 = (n x (n-1))/2
Big-O Example 2 (cont.)
Therefore T(n) = 1.5n2 – 1.5n
When n = 0, the polynomial has the
value 0
For values of n > 1, 1.5n2 > 1.5n2 – 1.5n
Therefore T(n) is O(n2) when n0 is 1 and
c is 1.5
Big-O Example 2 (cont.)
Symbols Used in Quantifying
Performance
Common Growth Rates
Different Growth Rates
Effects of Different
Growth Rates
Algorithms with Exponential and
Factorial Growth Rates
Algorithms with exponential and factorial
growth rates have an effective practical
limit on the size of the problem they can
be used to solve
With an O(2n) algorithm, if 100 inputs
takes an hour then,
101 inputs will take 2 hours
105 inputs will take 32 hours
114 inputs will take 16,384 hours (almost 2
years!)
Algorithms with Exponential and
Factorial Growth Rates (cont.)
Encryption algorithms take advantage of
this characteristic
Some cryptographic algorithms can be
broken in O(2n) time, where n is the
number of bits in the key
A key length of 40 is considered
breakable by a modern computer,
but a key length of 100 bits will take a
billion-billion (1018) times longer than a
key length of 40
Performance of
KWArrayList
The set and get methods execute in
constant time: O(1)
Inserting or removing general elements
is linear time: O(n)
Adding at the end is (usually) constant
time: O(1)
With our reallocation technique the average
is O(1)
The worst case is O(n) because of
reallocation
Single-Linked Lists
Section 2.5
Single-Linked Lists
A linked list is useful for inserting and
removing at arbitrary locations
The ArrayList is limited because its add
and remove methods operate in linear
(O(n)) time—requiring a loop to shift
elements
A linked list can add and remove
elements at a known location in O(1)
time
In a linked list, instead of an index, each
element is linked to the following
A List Node
A node can contain:
a data item
one or more links
A link is a reference to a list
node
In our structure, the node
contains a data field named
data of type E
and a reference to the next
node, named next
List Nodes for Single-
Linked Lists
private static class Node<E> {
private E data;
private Node<E> next;
/** Creates a new node with a null next field
@param dataItem The data stored
*/
private Node(E dataItem) {
data = dataItem;
next = null;
}
/** Creates a new node that references another node
@param dataItem The data stored
@param nodeRef The node referenced by new node
*/
private Node(E dataItem, Node<E> nodeRef) {
data = dataItem;
next = nodeRef;
}
}
List Nodes for Single-
Linked Lists
Node<String> e3 = new Node<String>(“Salem”, null);
Node<String> e2 = new Node<String>(“Khalid”, e3);
Node<String> e1 = new Node<String>(“Ali”, e2);
Node<String> e0 = new Node<String>(“Ahmed”, e1);
List Nodes for Single-
Linked Lists
Ali Ahmed Sale
m
1048 2046
null
028 1048 2046
List Nodes for Single-Linked Lists
(cont.)
private static class Node<E> { The keyword static
private E data; indicates that the
private Node<E> next; Node<E> class will not
reference its outer
/** Creates a new node with a null next field
class
@param dataItem The data stored
*/
private Node(E data) {
Static inner classes
data = dataItem; are also called nested
next = null; classes
}
/** Creates a new node that references another node
@param dataItem The data stored
@param nodeRef The node referenced by new node
*/
private Node(E dataItem, Node<E> nodeRef) {
data = dataItem;
next = nodeRef;
}
}
List Nodes for Single-Linked Lists
(cont.)
private static class Node<E> {
private E data;
private Node<E> next;
/** Creates a new node with a null next field
@param dataItem The data stored
*/
Generally, all details
private Node(E dataItem) { of the Node class
data = dataItem; should be private.
next = null; This applies also to
} the data fields and
constructors.
/** Creates a new node that references another node
@param dataItem The data stored
@param nodeRef The node referenced by new node
*/
private Node(E dataItem, Node<E> nodeRef) {
data = dataItem;
next = nodeRef;
}
}
Connecting Nodes
Connecting Nodes (cont.)
Node<String> tom = new Node<String>("Tom");
Node<String> dick = new Node<String>("Dick");
Node<String> harry = new Node<String>("Harry");
Node<String> sam = new Node<String>("Sam");
tom.next = dick;
dick.next = harry;
harry.next = sam;
A Single-Linked List
Class
Generally, we do not have individual
references to each node.
A SingleLinkedList object has a data
field head, the list head, which
references the first list node
public class SingleLinkedList<E> {
private Node<E> head = null;
private int size = 0;
...
}
SLList: An Example List
SLList<String> Node<String> Node<String>
head = next = next =
data = "Tom" data = "Dick"
Implementing SLList.addFirst(E item)
SLList<String> Node<String> Node<String>
head = next = next =
data = "Tom" data = "Dick"
Node<String>
next =
data = "Ann"
The element
added to the list
Implementing SLList.addFirst(E item) (cont.)
private void addFirst (E item) {
Node<E> temp = new Node<E>(item, head);
head = temp;
size++;
}
or, more simply ...
private void addFirst (E item) {
head = new Node<E>(item, head);
size++;
}
This works even if head is null
Implementing addAfter(Node<E> node,
E item)
SLList<String> Node<String> Node<String>
head = next = next =
data = "Tom" data = "Dick"
Node<String>
next =
data = "Ann"
The element
added to the list
Implementing addAfter(Node<E> node, E item)
(cont.)
private void addAfter (Node<E> node, E item) {
Node<E> temp = new Node<E>(item, node.next);
node.next = temp; We declare this method
size++; private since it should not
be called from outside the
} class. Later we will see
how this method is used to
implement the public add
or, more simply ... methods.
private void addAfter (Node<E> node, E item) {
node.next = new Node<E>(item, node.next);
size++;
}
Implementing removeAfter(Node<E>
node)
temp
SLList<String> Node<String> Node<String>
head = next = next =
data = "Tom" data = "Dick"
Node<String>
The Node
parameter next =
data = "Ann"
Implementing removeAfter(Node<E> node)
(cont.)
private E removeAfter (Node<E> node) {
Node<E> temp = node.next;
if (temp != null) {
node.next = temp.next;
size--;
return temp.data;
} else {
return null;
}
}
Implementing
SLList.removeFirst()
SLList<String> Node<String>
head = next =
data = "Dick"
Node<String>
next =
temp data = "Tom"
Implementing SLList.removeFirst()
(cont.)
private E removeFirst () {
Node<E> temp = head;
if (head != null) {
head = head.next;
}
if (temp != null) {
size--;
return temp.data
} else {
return null;
}
}
Traversing a Single-
Linked List
SLList<String> Node<String> Node<String>
head = next = next =
data = "Tom" data = "Dick"
Do
something
with nodeRef Node<String>
nodeRef
next = null
data = "Ann"
Traversing a Single-Linked List
(cont.)
toString()can be implemented with a traversal:
public String toString() {
Node<String> nodeRef = head;
StringBuilder result = new StringBuilder();
while (nodeRef != null) {
result.append(nodeRef.data);
if (nodeRef.next != null) {
result.append(" ==> ");
}
nodeRef = nodeRef.next;
}
return result.toString();
}
Traversing a Single-Linked List
(cont.)
public void FindMin() {
Node<Integer> nodeRef = head;
Integer min;
if(head != null)
min = head.data;
while (nodeRef != null) {
if(nodeRef.data < min)
min = noderef.data;
nodeRef = nodeRef.next;
}
}
SLList.getNode(int)
In order to implement methods required
by the List interface, we need an
additional helper method:
private Node<E> getNode(int index) {
Node<E> node = head;
for (int i=0; i<index && node != null; i++) {
node = node.next;
}
return node;
}
Completing the SingleLinkedList
Class
public E get(int index)
public E get (int index) {
if (index < 0 || index >= size) {
throw new
IndexOutOfBoundsException(Integer.toString(index));
}
Node<E> node = getNode(index);
return node.data;
}
public E set(int index, E newValue)
public E set (int index, E anEntry) {
if (index < 0 || index >= size) {
throw new
IndexOutOfBoundsException(Integer.toString(index));
}
Node<E> node = getNode(index);
E result = node.data;
node.data = anEntry;
return result;
}
public void add(int index, E item)
public void add (int index, E item) {
if (index < 0 || index > size) {
throw new
IndexOutOfBoundsException(Integer.toString(index));
}
if (index == 0) {
addFirst(item);
} else {
Node<E> node = getNode(index-1);
addAfter(node, item);
}
}
public boolean add(E item)
To add an item to the end of the list
public boolean add (E item) {
add(size, item);
return true;
}
public void add(int index, E item)
public void add (E item) {
if (size == 0) {
addFirst(item);
} else {
Node<E> node = getNode(size-1);
addAfter(node, item);
}
}
public void swapFirstLast()
public void swapFirstLast () {
if (size > =2) {
E first= head.data; //get(0);
Node<E> lastNode = head;
while(lastNode.next !=null)
lastNode = lastNode.next;
E last = lastNode.data; //get(size-1);
head.data = last; //set(0, last);
lastNode.data = first; //set(size-1, first);
}
}
public void swapFirstLast()
public void swapFirstLast () {
if (size > =2) {
E first= head.data;
Node<E> lastNode = head;
while(lastNode.next != null)
lastNode = lastNode.next;
head.data = lastNode.data;
lastNode.data = first;
}
}
public void removeMiddle()
public void removeMiddle () {
if (size %2==1) {
if(size >=1){
int middle = size/2;
remove(middle);
}
}
else{
if(size >=2){
int middle1 = size/2;
int middle2 = middle1-1; // size/2 - 1
remove(middle1);
remove(middle2);
}
}
}
public int indexOfMin()
public int indexOfMin () {
Node<E> node = head;
E min = head.data;
int indexMin=0; index =0 ;
while(node !=null){
if( node.data < min){
min = node.data;
indexMin = index;
}
node = node.next;
index++;
}
return indexMin;
}
public void indexOfMax()
public int indexOfMax () {
Node<E> node = head;
E max = head.data;
int indexMax = 0; index =0 ;
while(node !=null){
if( node.data > max){
max = node.data;
indexMax = index;
}
node = node.next;
index++;
}
return indexMax;
}
public void swapMinMax()
public void swapMinMax () {
if (size > =2) {
int minIndex = indexOfMin();
int maxIndex = indexOfMax();
E min= get(minIndex);
E max = get(maxIndex);
set(minIndex, max);
set(maxIndex, min);
}
}
Double-Linked Lists and Circular
Lists
Section 2.6
Double-Linked Lists
Limitations of a singly-linked list include:
Insertion at the front is O(1); insertion at
other positions is O(n)
Insertion is convenient only after a
referenced node
Removing a node requires a reference to
the previous node
We can traverse the list only in the forward
direction
We can overcome these limitations:
Add a reference in each node to the
Double-Linked Lists
(cont.)
Node Class
private static class Node<E> {
private E data;
private Node<E> next = null;
private Node<E> prev = null;
private Node(E dataItem) {
data = dataItem;
}
}
Inserting into a Double-
Linked List
from predecessor sam
Node Node
next = next = null
to
predecessor = prev = prev
data = "Harry" data = "Sam"
sharon
Node<E> sharon = new Node<E>("Sharon");
sharon.next = sam;
sharon.prev = sam.prev; Node
sam.prev.next = sharon;
sam.prev = sharon next =
= prev
data = "Sharon"
Removing from a Double-Linked List
harry
Node Node
next = next =
= prev = prev
data = "Dick" data = "Sharon"
Node
next =
harry.prev.next = harry.next = prev
harry.next.prev = harry.prev data = "Harry"
A Double-Linked List
Class
So far we have worked only
with internal nodes
As with the single-linked class,
it is best to access the internal
nodes with a double-linked list
object
A double-linked list object has data fields:
head (a reference to the first list Node)
tail (a reference to the last list Node)
size
Insertion at either end is O(1); insertion
elsewhere is still O(n)
public Node<E> minNode()
public Node<E> minNode () {
Node<E> node = head;
Node<E> minNode = head;
E min = head.data;
while(node !=null){
if( node.data < min){
min = node.data;
minNode = node;
}
node = node.next;
}
return minNode;
}
public Node<E> minNode()
public Node<E> minNode () {
Node<E> minNode = head;
Node<E> temp = head;
E min = head.data;
while(temp !=null){
if( temp.data < min){
min = temp.data;
minNode = temp;
}
temp = temp.next;
}
return minNode;
}
public void removeMinNode()
public void removeMinNode () {
Node<E> minNode = minNode ();
Node<E> p = minNode.prev;
Node<E> q = minNode.next;
if(tail == head){ tail = null; head =null; size=0;}
else if(minNode.prev ==null){
head = head.next;
head.prev = null; size--;
}
else if(minNode.next ==null){
tail = tail.prev;
tail.next = null; size--;
}
else{
p.next = q;
q.prev = p; size--;
}
}
Circular Lists
Circular double-linked list:
Link last node to the first node, and
Link first node to the last node
We can also build singly-linked circular lists:
Traverse in forward direction only
Advantages:
Continue to traverse even after passing the first or
last node
Visit all elements from any starting point
Never fall off the end of a list
Disadvantage: Code must avoid an infinite loop!
Circular Lists (cont.)
The LinkedList Class and the Iterator,
ListIterator, and Iterable Interfaces
Section 2.7
The LinkedList Class
The Iterator
An iterator can be viewed as a moving place
marker that keeps track of the current position in
a particular linked list
An Iterator object for a list starts at the first
node
The programmer can move the Iterator by
calling its next method
The Iterator stays on its current list item until it
is needed
An Iterator traverses in O(n) while a list
traversal using get() calls in a linked list is O(n2)
Iterator Interface
The Iterator interface is defined in
java.util
The List interface declares the method
iterator which returns an Iterator
object that iterates over the elements of
that list
Iterator Interface (cont.)
An Iterator is conceptually between
elements; it does not refer to a particular
object at any given time
Iterator Interface (cont.)
In the following loop, we process all
items in List<Integer> through an
Iterator
Iterator<Integer> iter = aList.iterator();
while (iter.hasNext()) {
int value = iter.next();
// Do something with value
...
}
Iterators and Removing
Elements
You can use the Iterator remove()method to
remove items from a list as you access them
remove() deletes the most recent element
returned
You must call next()before each remove();
otherwise, an IllegalStateException will be
thrown
LinkedList.remove vs. Iterator.remove:
LinkedList.remove must walk down the list each time,
then remove, so in general it is O(n2)
Iterator.remove removes items without starting over
at the beginning, so in general it is O(n)
Iterators and Removing Elements
(cont.)
To remove all elements from a list of type Integer
that are divisible by a particular value:
public static void removeDivisibleBy(LinkedList<Integer>
aList, int div) {
Iterator<Integer> iter = aList.iterator();
while (iter.hasNext()) {
int nextInt = iter.next();
if (nextInt % div == 0) {
iter.remove();
}
}
}
ListIterator Interface
Iterator limitations
Traverses List only in the forward direction
Provides a remove method, but no add
method
You must advance the Iterator using your
own loop if you do not start from the
beginning of the list
ListIterator extends Iterator,
overcoming these limitations
ListIterator Interface
(cont.)
As with Iterator, ListIterator is
conceptually positioned between
elements of the list
ListIterator positions are assigned an
index from 0 to size
ListIterator Interface
(cont.)
ListIterator Interface
(cont.)
Comparison of Iterator and
ListIterator
ListIterator is a subinterface of Iterator
Classes that implement ListIterator must
provide the features of both
Iterator:
Requires fewer methods
Can iterate over more general data
structures
Iterator is required by the Collection
interface
ListIterator is required only by the List
interface
Conversion Between
ListIterator and an Index
ListIterator:
nextIndex()returns the index of item to be
returned by next()
previousIndex() returns the index of item
to be returned by previous()
LinkedList has method listIterator(int
index)
Returns a ListIterator positioned so
next()will return the item at position index
Conversion Between
ListIterator and an Index
(cont.)
The listIterator (int index) method
creates a new ListIterator that starts
at the beginning, and walks down the list
to the desired position – generally an
O(n) operation
Enhanced for Statement
Java 5.0 introduced an enhanced for
statement
The enhanced for statement creates an
Iterator object and implicitly calls its
hasNext and next methods
Other Iterator methods, such as remove,
are not available
Enhanced for Statement
(cont.)
The following code counts the number of
times target occurs in myList (type
LinkedList<String>)
count = 0;
for (String nextStr : myList) {
if (target.equals(nextStr)) {
count++;
}
}
Enhanced for Statement
(cont.)
In list myList of type LinkedList<Integer>,
each Integer object is automatically
unboxed:
sum = 0;
for (int nextInt : myList) {
sum += nextInt;
}
Enhanced for Statement
(cont.)
The enhanced for statement also can be
used with arrays, in this case, chars or
type char[]
for (char nextCh : chars) {
System.out.println(nextCh);
}
Iterable Interface
Each class that implements the List interface must
provide an iterator method
The Collection interface extends the Iterable interface
All classes that implement the List interface (a
subinterface of Collection) must provide an iterator
method
Allows use of the Java 5.0 for-each loop
public interface Iterable<E> {
/** returns an iterator over the elements in this
collection. */
Iterator<E> iterator();
}
Implementation of a Double-
Linked List Class
Section 2.8
KWLinkedList
We will define a KWLinkedList class which implements
some of the methods of the List interface
The KWLinkedList class is for demonstration purposes
only; Java provides a standard LinkedList class in
java.util which you should use in your programs
KWLinkedList (cont.)
import java.util.*;
/** Class KWLinkedList implements a double linked list and
* a ListIterator. */
public class KWLinkedList <E> {
// Data Fields
private Node <E> head = null;
private Node <E> tail = null;
private int size = 0;
. . .
Add Method
1. Obtain a reference, /** Add an item at the specified
nodeRef, to the node at index.
position index @param index The index at
which the object is
2. Insert a new Node to be inserted
containing obj before the @param obj The object to be
node referenced by inserted
nodeRef @throws
IndexOutOfBoundsException
To use a ListIterator object to if the index is out
implement add: of range
3. Obtain an iterator that is (i < 0 || i > size())
It is not necessary to
positioned just before the */
declare a local
Node at position index public void add(int index, E obj)
ListIterator; the {
4. Insert a new Node
method call listIterator(index).add(obj);
containing obj before the
listIterator returns an }
Node currently referenced
anonymous
by this iterator
listIterator object
Get Method
1. Obtain a reference, /** Get the element at position
index.
nodeRef, to the @param index Position of
node at position item to be retrieved
index @return The item at index
*/
2. Return the contents
public E get(int index) {
of the Node return
referenced by listIterator(index).next();
nodeRef }
Other Add and Get
Methods
public void addFirst(E item) {
add(0, item);
}
public void addLast(E item) {
add(size, item);
}
public E getFirst() {
return head.data;
}
public E getLast() {
return tail.data;
}
Implementing the ListIterator
Interface
KWListIteris an inner class of KWLinkedList
which implements the ListIterator
interface
Implementing the ListIterator
Interface (cont.)
Implementing the ListIterator
Interface (cont.)
private class KWListIter implements ListIterator<E> {
private Node <E> nextItem;
private Node <E> lastItemReturned;
private int index = 0;
...
Constructor
public KWListIter(int i) {
// Validate i parameter.
if (i < 0 || i > size) {
throw new IndexOutOfBoundsException("Invalid index " + i);
}
lastItemReturned = null; // No item returned yet.
// Special case of last item
if (i == size) {
index = size;
nextItem = null;
}
else { // Start at the beginning
nextItem = head;
for (index = 0; index < i; index++) {
nextItem = nextItem.next;
}
}
}
The hasNext()Method
tests to see if nextItem is null
public boolean hasnext() {
return nextItem != null;
}
Advancing the Iterator
KWLinkedList Node Node Node
head next next next
tail prev null prev prev
size 3 data "Tom" data "Harry" data "Sam"
KWListIter
public E next() {
if (!hasNext()) {
nextItem throw new NoSuchElementException();
lastItemReturned }
index 12 lastItemReturned = nextItem;
nextItem = nextItem.next;
index++;
return lastItemReturned.data;
}
Previous Methods
public boolean hasPrevious() {
return (nextItem == null && size != 0)
|| nextItem.prev != null;
}
public E previous() {
if (!hasPrevious()) {
throw new NoSuchElementException();
}
if (nextItem == null) { // Iterator past the last element
nextItem = tail;
}
else {
nextItem = nextItem.prev;
}
lastItemReturned = nextItem;
index--;
return lastItemReturned.data;
}
The Add Method
When adding, there are four cases to
address:
Add to an empty list
Add to the head of the list
Add to the tail of the list
Add to the middle of the list
Adding to an Empty List
(after insertion)
if (head == null) {
head = new Node<E>(obj);
tail = head;
}
...
size++
Adding to the
Head of the
KWListIter
List
nextItem =
lastItemReturned = null
1
index = 0
Node Node Node
next = next = next = null
KWLinkedList null = prev = prev
= prev
data = "Tom" data = "Harry" data = "Sam"
head = null
tail = null
size = 3
4
if (nextItem == head) {
Node<E> newNode = new Node<E>(obj);
newNode.next = nextItem;
Node nextItem.prev = newNode;
head = newNode;
next = null }
null = prev ...
newNode
data = "Ann" size++;
index++;
KWListIter
Adding to the
nextItem = null
lastItemReturned = null
Tail of the List
index = 23 Node Node Node
next = next = next = null
prev = null = prev = prev
KWLinkedList data = "Tom" data = "Ann" data = "Sam"
head = null
tail = null
4
size = 3
if (nextItem == null) { Node
Node<E> newNode = new Node<E>(obj);
tail.next = newNode; next = null
newNode.prev = tail; null = prev
newNode
tail = newNode data = "Bob"
}
...
size++;
index++;
KWListIter
Adding to the
nextItem = null
lastItemReturned = null
Middle of the List
index = 12 Node Node Node
next = next = next = null
prev = null = prev = prev
KWLinkedList data = "Tom" data = "Ann" data = "Sam"
head = null
tail = null
4
size = 3
Node
else {
Node<E> newNode = new Node<E>(obj); next = null
newNode.prev = nextItem.prev; null = prev
nextItem.prev.next = newNode; data = "Bob"
newNode.next = nextItem;
nextItem.prev = newNode;
} newNode
...
size++;
index++;
Inner Classes: Static and Nonstatic
KWLinkedList contains two inner classes:
Node<E> is declared static: there is no need for it to
access the data fields of its parent class, KWLinkedList
KWListIter cannot be declared static because its
methods access and modify data fields of
KWLinkedList’s parent object which created it
An inner class which is not static contains an implicit
reference to its parent object and can reference the
fields of its parent object
Since its parent class is already defined with the
parament <E>, KWListIter cannot be declared as
KWListIter<E>; if it were, an incompatible types
syntax error would occur
The Collections Framework Design
Section 2.9
The Collection Interface
Specifies a subset of methods in the List
interface, specifically excluding
add(int, E)
get(int)
remove(int)
set(int, E)
but including
add(E)
remove(Object)
the iterator method
The Collection
Framework
Common Features of
Collections
Collections
grow as needed
hold references to objects
have at least two constructors: one to
create an empty collection and one to make
a copy of another collection
Common Features of Collections
(cont.)
In a general For collections
implementing the List
Collection the
interface, the order of
order of elements the elements is
is not specified determined by the
index
Common Features of Collections
(cont.)
In a general In ArrayList and
Collection, the LinkedList, add(E)
position where an always inserts at
object is inserted is the end and always
not specified returns true
AbstractCollection, AbstractList,
and AbstractSequentialList
The Java API includes several "helper"
abstract classes to help build
implementations of their corresponding
interfaces
By providing implementations for
interface methods not used, the helper
classes require the programmer to
extend the AbstractCollection class
and implement only the desired methods
Implementing a Subclass of
Collection<E>
Extend AbstractCollection<E>, which
implements most operations
You need to implement only:
add(E)
size()
iterator()
an inner class that implements Iterator<E>
Implementing a Subclass of
List<E>
Extend AbstractList<E>
You need to implement only:
add(int, E)
get(int)
remove(int)
set(int, E)
size()
AbstractList implements Iterator<E>
using the index
AbstractCollection, AbstractList,
and AbstractSequentialList
Another more complete way to declare
KWArrayList is:
public class KWArrayList<E> extends AbstractList<E>
implements List<E>
Another more complete, way to declare
KWLinkedLinkedList is:
public class KWLinkedList<E> extends
AbstractSequentialList<E>
implements List<E>
List and RandomAccess Interfaces
Accessing a LinkedList using an index
requires an O(n) traversal of the list until
the index is located
The RandomAccess interface is applied to
list implementations in which indexed
operations are efficient (e.g. ArrayList)
An algorithm can test to see if a
parameter of type List is also of type
RandomAccess and, if not, take appropriate
measures to optimize indexed operations
Application of the LinkedList Class
Section 2.10
An Application: Ordered
Lists
We want to maintain a list of names in
alphabetical order at all times
Approach
Develop an OrderedList class (which can be
used for other applications)
Implement a Comparable interface by providing
a compareTo(E) method
Use a LinkedList class as a component of the
OrderedList
if OrderedList extended LinkedList, the user could
use LinkedList's add methods to add an element
out of order
Class Diagram for
OrderedList
Design
Inserting into an
OrderedList
Strategy for inserting new element e:
Find first item > e
Insert e before that item
Refined with an iterator:
Create ListIterator that starts at the
beginning of the list
While the ListIterator is not at the end of the
list and e >= the next item
Advance the ListIterator
Insert e before the current ListIterator
position
Inserting Diagrammed
Inserting Diagrammed
(cont.)
OrderedList.add
public void add (E e) {
ListIterator<E> iter = theList.listIterator();
while (iter.hasNext()) {
if (e.compareTo(iter.next()) < 0) {
// found element > new one
iter.previous(); // back up by one
iter.add(e); // add new one
return; // done
}
}
iter.add(e); // will add at end
}
Using Delegation to Implement the
Other Methods
public E get (int index) {
return theList.get(index);
}
public int size () {
return theList.size();
}
public E remove (E e) {
return theList.remove(e);
}
// returns an iterator positioned before the first element
public Iterator iterator() {
return theList.iterator();
}