First provide SinglyLinkedListElement class representing the nodes:
public class SinglyLinkedListElement { protected Object data; // value stored in this element protected SinglyLinkedListElement nextElement; // ref to next element // constructors public SinglyLinkedListElement(Object v, SinglyLinkedListElement next) // post: constructs a new element with value v, // followed by next { data = v; nextElement= next; } public SinglyLinkedListElement(Object v) // post: constructs a new element of a list with value v // but with nothing attached. { this(v,null); } public SinglyLinkedListElement next() // post: returns reference to next value in list { return nextElement; } public void setNext(SinglyLinkedListElement next) // post: sets reference to new next value { nextElement = next; } public Object value() // post: returns value associated with this element { return data; } public void setValue(Object value) // post: sets value associated with this element { data = value; } public String toString() // post: returns string representation of element { return "<SinglyLinkedListElement: "+value()+">"; } }
public class SinglyLinkedList implements List { protected SinglyLinkedListElement head; // first elt protected int count; // list size public SinglyLinkedList() // post: generates an empty list. { head = null; count = 0; } public void add(Object value) // post: adds value to beginning of list. { addToHead(value); } public void addToHead(Object value) // post: add value to beginning of list. { // note the order that things happen: // head is parameter, then assigned!!! head = new SinglyLinkedListElement(value, head); count++; } public Object removeFromHead() // pre: list is not empty // post: removes and returns value from beginning of list { SinglyLinkedListElement temp = head; head = head.next(); // move head down the list count--; return temp.value(); } public void addToTail(Object value) // post: adds value to end of list { // location for the new value SinglyLinkedListElement temp = new SinglyLinkedListElement(value,null); if (head != null) { // pointer to possible tail SinglyLinkedListElement finger = head; while (finger.next() != null) finger = finger.next(); finger.setNext(temp); } else head = temp; count++; } public Object removeFromTail() // pre: list is not empty // post: last value in list is returned { // keep two ptrs w/ previous one elt behind finger SinglyLinkedListElement finger = head; SinglyLinkedListElement previous = null; Assert.pre(head != null,"List is empty!"); while (finger.next() != null) // find end of list { previous = finger; finger = finger.next(); } // finger is null, or points to end of list if (previous == null) // list had 1 element head = null; else // pointer to last element reset to null. previous.setNext(null); } count--; return finger.value(); } public Object peek() ... public Object tailPeek() // find end of list as in removeFromTail public boolean contains(Object value) // pre: value is not null // post: returns true iff value is found in list. { SinglyLinkedListElement finger = head; while (finger != null && !finger.value().equals(value)) finger = finger.next(); return finger != null; } public Object remove(Object value) // pre: value is not null // post: removes 1st element with matching value, if any. { SinglyLinkedListElement finger = head; SinglyLinkedListElement previous = null; while (finger != null && !finger.value().equals(value)) { previous = finger; finger = finger.next(); } // finger points to target value if (finger != null) { // we found the element to remove if (previous == null) // it is first head = finger.next(); else // it's not first previous.setNext(finger.next()); count--; return finger.value(); } // didn't find it, return null return null; } public int size() // post: returns the number of elements in list { return count; } public boolean isEmpty() // post: returns true iff the list is empty { return size() == 0; } public void clear() // post: removes all elements from the list { head = null; count = 0; } }
Most common errors in working with linked structures are ignoring these cases! (Recall the CircularVector class you implemented and the special cases you needed to handle when removing elements.)
size(), isEmpty(), peek() // O(1) in both tailPeek(), removeFromTail() // O(1) in Vector, O(n) in Linked clear() addToHead(Object value) removeFromHead() // O(n) in Vector, O(1) in Linked contains(Object value) remove(Object value) // O(n) in both addToTail(Object value) // O(n) in Linked, // varies in Vector - usually O(1)
If list of size n kept in vector, then if "tight fit" (underlying array has exactly n elements), then need n*words(value), where words(value) is the amount of space necessary to hold a value stored in the list (including the initial reference). But underlying array may be much, much larger (remember underlying array never shrinks), so may use much more space.
Linked list representation is more predictable: space for count & head, and then for each node, space for value plus one reference.
Linked always O(n), Vector usually so.
Note. If we didn't keep count field, size operation would become O(n) in Linked list, but would save time to update count in remove and add operations.
public class CircularList implements List { protected SinglyLinkedListElement tail; protected int count; public CircularList() // pre: constructs a new circular list { tail = null; count = 0; } public void add(Object value) // post: adds value to beginning of list. { addToHead(value); } public void addToHead(Object value) // pre: value non-null // post: adds element to head of list { SinglyLinkedListElement temp = new SinglyLinkedListElement(value); if (tail == null) { tail = temp; tail.setNext(tail); } else { temp.setNext(tail.next()); tail.setNext(temp); } count++; } public void addToTail(Object value) // pre: value non-null // post: adds element to tail of list { addToHead(value); tail = tail.next(); // moves new from head to tail } public Object peek() // pre: !isEmpty() // post: returns value at head of list { return tail.next().value(); } public Object tailPeek() // pre: !isEmpty() // post: returns value at tail of list { return tail.value(); } public Object removeFromHead() // pre: !isEmpty() // post: returns and removes value from head of list { SinglyLinkedListElement temp = tail.next(); // ie. the head of the list if (tail == tail.next()) // 1 elt in list tail = null; else { tail.setNext(temp.next()); temp.setNext(null); // helps clean things up } // temp is free count--; return temp.value(); } public Object removeFromTail() // pre: !isEmpty() // post: returns and removes value from tail of list { Assert.pre(!isEmpty(),"The list is not empty."); SinglyLinkedListElement finger = tail; while (finger.next() != tail) finger = finger.next(); // finger now points to second-to-last value SinglyLinkedListElement temp = tail; if (finger == tail) tail = null; else { finger.setNext(tail.next()); tail = finger; } count--; return temp.value(); } public boolean contains(Object value) // pre: value != null // post: returns true if list contains value, else false { if (tail == null) return false; SinglyLinkedListElement finger; finger = tail.next(); while ((finger != tail) && (!finger.value().equals(value))) finger = finger.next(); return finger.value().equals(value); } public Object remove(Object value) // pre: value != null // post: remove & returns element equal to value, or null { if (tail == null) return null; SinglyLinkedListElement finger = tail.next(); SinglyLinkedListElement previous = tail; int compares; for (compares = 0; (compares < count) && (!finger.value().equals(value)); compares++) { previous = finger; finger = finger.next(); } if (finger.value().equals(value)) { // an example of the pigeon-hole principle if (tail == tail.next()) tail = null; else { if (finger == tail) tail = previous; previous.setNext(previous.next().next()); } // finger value free finger.setNext(null) // to keep things disconnected count--; // fewer elements return finger.value(); } else return null; } public int size() // post: returns number of elements in list { return count; } public boolean isEmpty() // post: returns true if no elements in list { return tail == null; } public void clear() // post: removes all elements from list. { count = 0; tail = null; } }