Tests, Tests, Everywhere
The class decided to hold a review session Sunday evening at 8:00PM in the cluster -- see ya there! Everyone was encouraged to review my old exams on the Web, focusing ont he linked lsit and vector questions, de-emphasizing Java syntax (remember, the mission of 15-200 has changed).I volunteer you guys to beta-test a working-draft of the end of semester exam. It won't be the same question -- and the format is a draft, not the final product. But, it should still be good practice. We'll do that during next week's recitation. It will involve writing methods internal to a singly linked list, without the benefit of other linked list methods.
Limitations of Singly-Linked Lists
One major advantage of linked lists over vectors is that we can remove an individual node, and the garbage collector will eventually pick it up. We can't reduce the size of a vector.A downside of linked lists is that we don't have indexed access to the nodes. If I want a node in the middle of the list, I have to start from the beginning of the list.
Linked lists have two other problems. The first is that we can't go backwards. The reference in each node names only the next item in the list.
The second problem is that if we need to remove, say, node b, our
index
must reference node a, and then we must refer toindex.getNext().getNext()
in order to connect node a's "next" reference to node c (dereferencing node b and effectively removing it from the linked list).Is there another way? There is, but it requires a list with one extra reference in each node, a doubly-linked list. 3
Doubly-Linked Lists
Doubly-Linked lists are lists which contain slightly different nodes than the ones found in singly-linked lists. Each node references its predecessor as well as its successor. This allows us to look and move both forward and backward in the list.Here's a doubly-linked list. Each node has an extra reference, which we will call
prev
, to the node that comes before it in the linked list (its predecessor).Take a look at Node b. It's prev reference refers to Node a, and its next reference refers to Node c.
Look at Node a. The dashed line in Node a's
prev
signifies the fact that, because Node a is the first node in the list, itsprev
isnull
. Look at Node d. The dashed line in Node d's next signifies the fact that, since Node d is at the end of the list, itsnext
isnull
.
null
is exactly what it sounds like - nothing. This means that you cannot refer tonull.getNext()
ornull.getPrev()
!!!The great thing about doubly-linked lists is that, if you'd like to, say, remove the node in the above list containing "Mary", you can set index to refer directly to node b and delete it. How do you do this?
The thing to notice here is that there is no longer a reference to Node b, because Nodes a and c have been reset to reference each other.
Index
has been reset to refer to the first node in the list.Let's look at the doubly-linked list class. It's much like the singly-linked list class. The nodes have an extra reference, a reference to their predecessors in the list. And the methods are different because we now have one more reference to worry about.
The
Node
ClassInstance Variables
private class Node { private Object data; private Node next; private Node prev; //constructors and methods }prev
is just likenext
, except thatprev
refers to the node's predecessor.Constructors
Take a another look at the second two constructors. What's the scope of
public Node() { data = null next = null; prev = null; } public Node (Object data) { this.data = data; next = prev = null; } public Node (Object data, Node prev, Node next) { this.data = data; this.next = next; this.prev = prev; }data
? What's the scope ofthis.data
? Remember thatthis
refers to the object you've just made with thenew
operator. We want to have meaningful variable names, so these variables have the same name. Just keep the distinction between the data being passed into the method and the instance variable of the object.Accessors
public Object getData () { return data; } public Node getPrev() { return prev; } public Node getNext() { return next; }Mutators
We could leave
public void setData (Object data) { this.data = data; }setData()
out of the class definition, because we can always just create a new node every time we want to change something. But that would be more work. When we make a new node, we'll want to set itsnext
andprev
references.As with singly-linked lists, the
public void setPrev (Node prev) { this.prev = prev; } public void setNext (Node next) { this.next = next; }Node
class is a private subclass of the doubly-linked list class. Here's the rest of the doubly-linked list class. The doubly-linked list class is like the singly-linked list class - except that there's one more reference to worry about.Instance Variables
private Node head; private Node tail; private Node index;Constructor
public DoublyLinkedList() { head = tail = index = null; }Adding a Node to the Beginning of the List
Here,
public void addHead(Object data) { // Create new node Node newNode = new Node (data, null, head); // Special case: Empty List if (null == head) { head = tail = index = newNode; return; } // Common case head.setPrev(newNode); head = newNode; }newNode
has been added to the front of the list.Adding a Node to the End of the List
public void addTail (Object data) { // Create new node Node newNode = new Node (data, tail, null); // Special case: Empty list if (tail == null) { tail = head = index = newNode; return; } // Common case tail.setNext (newNode); tail = newNode; }
Here, newNode has been added to the end of the list.
public void addBeforeIndex(Object data) throws IndexException
{
// Special case: Index is null
if (null == index)
throw new IndexException();
// Create new node
Node newNode = new Node (data, index, index.getNext());
// Special case: index is tail (we could call addTail)
if (index == tail)
{
index.setNext(newNode);
tail = newNode;
return;
}
// Common Case
index.getNext.setPrev (newNode);
index.setNext(newNode);
}
public void addBeforeIndex(Object data)
{
// Special case: Index is null
if (null == index)
throw new IndexException();
// Create new node
Node newNode = new Node (data, index.getPrev(), index);
//Special case: Index is head (we could call addHead)
if (head == index)
{
index.setPrev (newNode);
head = newNode;
return;
}
// Common case
index.getPrev.setNext (newNode);
index.setPrev (newNode);
}
Here, newNode has been added between nodes b and c. Index has been reset
to the beginning of the list.
index
Back to the Beginning of the List
public void resetIndex()
{
index = head;
}
You'll want to do this after you've finished deleting a node, because, if index
still
refers to your "deleted" node, you haven't deleted the node (because index
still refers
to it).
Linked lists don't have the same indexing capabilities that vectors have, but we
can write methods to compensate for this weakness.
public Object getHead()
{
if (null == head)
return null;
return head.getData();
}
public Object getTail()
{
if (null == tail)
return null;
return tail.getData()
}
public Object getIndexedData()
{
if (null == index)
return null;
return index.getData();
}
Modifying the SinglyLinkedList Code from Class
As an exercise in thinking through LinkedLists, we modified the code from last class to convert it from a singly linked list to a doubly linked list.We chose to modify the code for the two classes directly. But we could also have, arhuably should have, extended the other class using inheritence. This would allow us to directly reuse some of the common code and clearly express the relationship, "A doubly linked list is a linked list".
But, as it turns out, we wouldn't actually get to reuse as much as we might hope -- and this way matches the end-of-semester exam. It is also good practice for next week's exam.
Modifying the SinglyLinkedList Code From Class: DLinkedListNode
public class DLinkedListNode { private Comparable data; private DLinkedListNode next; private DLinkedListNode prev; public DLinkedListNode() { data = null; next = null; prev = null; } public DLinkedListNode (Comparable data, DLinkedListNode prev, DLinkedListNode next) { this.data = data; this.next = next; this.prev = prev; } public DLinkedListNode (Comparable data) { this.data = data; this.next = null; this.prev = null; } public void setNext (DLinkedListNode next) { this.next = next; } public void setPrev (DLinkedListNode prev) { this.prev = prev; } public Comparable getData() { return data; } public DLinkedListNode getNext() { return next; } public DLinkedListNode getPrev() { return prev; } }
Modifying the SinglyLinkedListCode from Last Class: DLinkedList
public class DLinkedList { private DLinkedListNode head; private DLinkedListNode tail; private DLinkedListNode index; public class DLinkedListException extends Exception { public DLinkedListException (String msg) { super(msg); } } public DLinkedList() { head = tail = index = null; } public void prepend (Comparable data) { head = new DLinkedListNode (data, null, head); if (head.getNext() != null) head.getNext().setPrev(head); if (null == tail) tail = head; if (null == index) index = head; } public void append (Comparable data) { if (null == tail) head = index = tail = new DLinkedListNode(data); else { tail.setNext (new DLinkedListNode (data,tail,null)); tail = tail.getNext(); } } public void resetIndex() { index = head; } public Comparable getIndex() throws DLinkedListException { if (null == index) throw new DLinkedListException ("Null index in getIndex()"); return index.getData(); } public void advanceIndex() throws DLinkedListException { if ((index == null) || (null == index.getNext())) throw new DLinkedListException ("Null index in advanceIndex()"); index = index.getNext(); } public void reverseIndex() throws DLinkedListException { if ( (null == index) || (index == head) ) throw new DLinkedListException ("Index is null or has no predecessor"); index = index.getPrev(); } public Comparable deleteAtIndex() throws DLinkedListException { Comparable data; try { data = index.getData(); reverseIndex(); // data = index.getNext().getData(); // Ugly, but avoids NPE index.setNext(index.getNext().getNext()); index.getNext().setPrev(index); } catch (NullPointerException npe) { throw new DLinkedListException ("index is null; can't delete"); } catch (DLinkedListException lle) { // No predecessor; first node in list head = index.getNext(); if (head != null) head.setPrev(null); } // Index was the tail if (index.getNext() == null) { tail = index; } // Move index to the one after the one we're deleting index = index.getNext(); return data; } public Comparable removeNth (int n) // beginning with 0th throws DLinkedListException { DLinkedListNode index; // Don't destroy user's index int count; if (null == head) throw new DLinkedListException ("Can't delete from empty list"); if (n == 0) { if (tail == head) tail = null; if (index == head) index = null; head = head.getNext(); if (head != null) head.setPrev (null); } else { try { for (index=head, count=0; count <= n; count++, index=index.getNext()) ; } catch (NullPointerException npe) { throw new DLinkedListException ("Less than n nodes on removeNth"); } if (index == tail) throw new DLinkedListException ("Less than n nodes on removeNth"); if (this.index == index) this.index = null; if (tail == index) tail = index.getPrev(); // same as tail=tail.getPrev() index.getPrev().setNext(index.getNext()); if (tail != index) index.getNext().setPrev(index.getPrev()); } } } /* DLinkedList */