Readings List Implementations Chapter 20.2 Objectives Learn how to implement the List interface Understand the efficiency trade-offs between the ArrayList and LinkedList implementations Additional references: Online Java Tutorial at java.sun.com/docs/books/tutorial/collections/ General-Purpose Implementations A set of implementations for the Collection interfaces provided in java.util: Interface Set List Map Implementation HashSet, TreeSet, ArrayList, LinkedList, HashMap, TreeMap, More than one implementation is provided for each interface: each implementation uses a different data structure each is optimized for some operations, but might not be so good for others users can define their own implementations if they need to List Implementations 2
General-Purpose Implementations (continued) Each implementation of Collection has a default constructor a constructor with a Collection parameter (used to transform one type of collection to another) Sorted collections have two more constructors one that takes a Comparator another that takes a SortedCollection List Implementations 3 ArrayList Implementation: CArrayList List elements are stored in an array in positions 0 to size-1 The get and set positional access operations are O(1). We can access items in the list in a random order much like the tracks on a CD. We call such a structure a random access data structure. Adding at the end is O(1) if there is space O(n) if the array is full o need to create a larger array and copy the n elements of the old array o can use System.arraycopy() to copy the array efficiently If we have an estimate of the max size of the list, we can create a large enough array using CArrayList(int initialcapacity) List Implementations 4
Array Implementation: CArrayList Adding and removing an element at a given index is expensive we need to shift part of the array both operations are O(n) on average removing using an iterator is also O(n) on average List Implementations 5 CArrayList Implementation Example public class CArrayList<E> { public static final int INIT_CAPACITY = 10; private static final int INCREMENT_FACT = 2; private E[] data; private int count; private int capacity; public CArrayList() { data = (E[]) new Object[INIT_CAPACITY]; count = 0; capacity = INIT_CAPACITY; List Implementations 6
CArrayList Example (cont d) public boolean add(e element) { // if array is full, increase its capacity if (count == capacity) { growarray(); data[count] = element; count++; return true; List Implementations 7 CArrayList Example (cont d) private void growarray() { capacity *= INCREMENT_FACT; E[] temp = (E[]) new Object[capacity]; System.arraycopy(data, 0, temp, 0, count); data = temp; // rest of implementation available for // download from course web site List Implementations 8
Array Implementation: CArrayList The array based implementation of our list has one very nice feature: we can get an item at a particular index in O(1) time and some disadvantages: it takes O(n) time on average to insert or remove from the list expanding the list is an O(n) operation if the list fills up In the next section we examine another implementation of the List interface that removes the above disadvantages but also loses the O(1) time for accesses. List Implementations 9 Singly Linked List A singly linked list (or just linked list) consists of a series of nodes Each node has two fields: the data stored in that node a link (reference) to the next node in the list the link in the last node is null the list is accessed through a reference to its first node (head) Example: A linked list of integers: head 10 20 30 Initially, list is empty; nodes are added one at a time as needed List Implementations 10
Singly Linked List A linked list has the advantage that it grows or shrinks dynamically as items are added to or removed from the list. It has the disadvantage that you cannot get an element in the list in O(1) time. If you want to get the item at the end of the list, you have to traverse the entire list to reach it. We call such a structure a sequential access data structure. Before we consider a full-blown implementation of the List interface using a linked list, we will learn how to perform a few basic operations on a linked list like inserting an item at the head of the list, inserting at the end of the list and removing an item from the list. List Implementations 11 Java Declaration of Singly Linked List Need to define a class Node to represent a node in the linked list. This class will be a private static inner class of CLinkedList - the class that implements the linked list. We make it static because instances of the inner class do not need to access any of the data associated with the outer class. Given that the class is a private inner class, it is not accessible outside of the containing class. List Implementations 12
Java Declaration of Node class private static class Node<E> { E element; Node<E> next; Node() { this(null, null); Node( E obj ) { this(obj, null); Node(E obj, Node nxt) { element = obj; next = nxt ; List Implementations 13 Linked List Class We now consider the implementation of some of the methods of the CLinkedList class: public class CLinkedList<E> { private Node<E> head; private Node<E> tail; private int numitems; public CLinkedList() { head = tail = null; numitems = 0; List Implementations 14
Linked List Operations cont d // add to end of list void add(e obj) { Node<E> newnode = new Node<E>(obj); Node<E> cur = head; if (cur == null) { head = tail = newnode; else { tail.next = newnode; tail = newnode; numitems++; List Implementations 15 Linked List Operations cont d When we remove an object from a list, not only we need a reference to the node containing the object we want to remove, but we also need a reference to the previous node so that we keep update the next links correctly. boolean remove(e obj) { Node<E> cur = head; Node<E> prev = null; // find a reference to the node containing obj // and to the previous node while(cur!= null &&!cur.element.equals(obj)) { prev = cur; cur = cur.next; List Implementations 16
Linked List Operations cont d // if we failed to find obj in list if (cur == null) return false; if (cur == head && head == tail) head = tail = null; else if (cur == head) head = cur.next; else if (cur == tail) { tail = prev; tail.next = null; else prev.next = cur.next; numitems--; return true; List Implementations 17 Doubly Linked List Singly linked lists have the following disadvantages: you can traverse the list in only one direction o Recall that in the Java collections framework, the List interface allows a client to get a ListIterator over the list and that this iterator can move either forwards or backwards through the list. if you have a reference to a node that you want to delete or you have a reference to a node and want to insert a new node before it, you have to traverse the list to find a pointer to the previous node. A doubly linked list is a variation of a linked list that does not have the disadvantages listed above. List Implementations 18
Doubly Linked List (cont'd) In a doubly linked list each node has two links: one to the next node, one to the previous node Example: A doubly linked list of integers: head 10 20 30 In last node, link to next node is null In first node, link to previous node is null Now we can easily find the previous node traverse the list in reverse order List Implementations 19 Java Declaration of Doubly Linked List private static class Node<E> { E element; Node<E> previous; Node<E> next; Node() { this(null, null, null) ; Node(E obj) { this(obj, null, null) ; Node(E obj, Node prv, Node nxt) { element = obj; previous = prv; next = nxt; List Implementations 20
Doubly Linked List Class We now consider the implementation of some of the methods of the GLinkedList class: public class GLinkedList<E> { private Node<E> head; private Node<E> tail; private int numitems; public GLinkedList() { head = tail = null; numitems = 0; List Implementations 21 Some Operations on Doubly Linked Lists // add to end of list void add(e obj) { Node<E> newnode = new Node<E>(obj, tail, null); if( head == null ) head = tail = newnode; else { tail.next = newnode; tail = newnode; numitems++; List Implementations 22
Some Operations cont d Remove an object from the list (removes only first instance found): boolean remove(e obj) { Node<E> cur = head; // look for a node containing obj while (cur!= null && cur.element.equals(obj)) cur = cur.next; // if we failed to find obj in the list if(cur == null) return false; List Implementations 23 Some Operations cont d if (cur == head && head == tail) head = tail = null; else if (cur == head) { head = cur.next; head.prev = null; else if (cur == tail) { tail = cur.prev; tail.next = null; else { cur.next.previous = cur.previous; cur.previous.next = cur.next; numitems--; return true; List Implementations 24
Linked List Implementation of List: GLinkedList GLinkedList is in Examples : implements List interface uses a doubly linked list with references to the head and tail nodes uses modcount to count # of modifications o an iterator uses this to detect concurrent modifications GListIterator (in same example) implements ListIterator interface keeps track of o last node returned by next() o next node to be returned by next() o # of modifications done by the iterator GLinkedList is very similar to Java s LinkedList but LinkedList uses a circular doubly linked list with a head node List Implementations 25 GLinkedList (cont ) After executing: List<Integer> glist = new GLinkedList<Integer>(); Iterator<Integer> itr = glist.iterator(); glist.add(new Integer(5)); glist.add(new Integer(6)); glist.add(new Integer(7)); itr.next(); List Implementations 26
GLinkedList (cont d) glist and itr look like: head tail size modcount glist 3 3 lastnode nextnode nextindex ExpectedModCount itr 1 3 5 6 7 List Implementations 27 Exercises Chapter 20, page 771 Exercises P20.3, P20.5 P20.9 starting from the GlinkedList class provided on the Web site. List Implementations 28