Return to the lecture notes index

Lecture 12 (October 2, 2006)

Doubly Linked List, Overview

A DoublyLinkedList is linked with both a next and a previous. We can traverse the list forward from the head to the tail, or backward from the tail to the head. I could walk until I find the node and then removing it becomes pretty simple.

  before removal:
  node1 *lt;==> node2 <==> node3 <=>=
  
  first we set node1's next to point to node3
    ________________________
    |                      v
   node1 <-- node2 <==> node3
  
 
  then we set node3's prev to point at node1
   _______________________
   |                      v
  node1     node2      node3
   ^                      |
   |______________________|

  
   index.getPrev().setNext(index.getNext());
   index.getNext().setPrev(index.getPrev());

Storage Overhead

In order to manage the data or an object we need to have a reference to it. This reference is a type of nondata overhead. In a singly linked list this requires twice as much nondata overhead, as opposed to just an instance of data, because we need a reference to the object and a reference to the next node.

With the doubly linked list there is a third reference and it is more nondata overhead waste. Usually the objects we work with are complicated and so the overhead is small in comparison.

Time Overhead

In reality the cost comes from maintaning the extra pointer. It needs to be kept up-to-date on every mutation. When we dont need it, it doubles the cost, but when we use it to avoid a traversal, it takes an O(n) operation down to O(1) -- a bit win.

Overall it is usually beneficial to use a DoublyLinkedList. This is different in other environments though. Sometimes LinkedLists did not hold a reference to the object but the object themselves. For instance in C where there can be a linkedlist of characters. The characters were much smaller in size when compared to the references on either side. So the overhead of a doubly linked list would have been very large by comparison.

The Code

Today we converted out LinkedList from the last couple of classes into a DoublyLinkedList. We also added an iterator() method and a DLLIterator inner class. This was an example of how iterators can be constructed via the Iterator interface.

the file belwo can also be found here: DoublyLinkedList.java


class DoublyLinkedList {

  public class DLLIterator implements Iterator {
    private Node index;
    
    public DLLIterator(Node head) {
      index = head;
    }
    
    public Object next() {
      Object saved = index.getItem();
      index = index.getNext();
      return saved;
    }
    
    public boolean hasNext() {
      return (index != null);
    }
  
  }
  

  class private Node {
  
    private Object item;
    private Node prev;
    private Node next;
    
    public Node (Node prev, Object item, Node next) {
      this.prev = prev;
      this.item = item;
      this.next = next;
    }
    
    
    public Node (Object item) {
      this.prev = null;
      this.item = item;
      this.next = null;
    }
    
    
    public Object getItem() { return item; }
    public Node getPrev() { return prev; }
    public Node getNext() { return next; }
    
    public void setPrev(Node prev) { this.prev = prev; }
    public void setNext(Node next) { this.next = next; }
    
    public String toString() {
      String retString = "[";
      
      // Mark beginning
      if (this.prev != null)
        retString += this.prev.getItem() + ", ";
      else 
        retString += "null, ";
        
      retString += this.getItem() + ", ";

      if (this.next != null)     
        retString += this.next.getItem() + "]";
      else
        retString += "null]";
    
      return retString;
    }

    
    public boolean equals (Object o) {
      Node n = (Node) o;
   
      if (!this.getItem().equals(n.getItem()))
        return false;
      
      if (this.getNext() != n.getNext())
        return false;
  
      if (this.getPrev() != n.getPrev())
        return false;
  
      return true;
    }
  
  
    private Node head;
    private Node tail;
    private int count;
    
    
    public DoublyLinkedList() {
      head = tail = null;
      count = 0;
    }
    
  
    public void addFirst(Object o) {
      head = new Node (null, o, head);
      if (head.getNext() != null)
        head.getNext().setPrev(head);
    
      if (tail == null) tail = head;
      count++;
    }
    
    
    public void addLast(Object o) {
    
      if (tail == null) {
        addFirst(o);
        // Count increased in addFirst() 
        return;
      }

      tail.setNext(new Node (tail, o, null));
      tail = tail.getNext();
      count++;
      
    }
    
    
    public Object getFirst() {  
      if (head == null)
        return null;
        
        return head.getItem();
    }
    
    
    public Object getLast() {  
      if (tail == null)
        return null;
        
        return tail.getItem();
    }
    
    public Object getNth (int n) {
    
      Node index;
      int count;
      for (index=head, count=0; ((index!=null)&&(count<n)); index=index.getNext(), count++)
      ;
      
      if (index == null)
        return null;
    
      return index.getItem();
    }
    
    
  public int indexOf (Object o) {    
    int posn;
    Node index;
    
    for (index=head, posn=0;
      ((index!=null) && (!index.getItem().equals(o)));
        index=index.getNext(), posn++ )
    ;
    
    if (posn > (this.count-1))
      return -1;
    
    return posn;
  }
  
  public boolean insertAtN (Object o, int n) {
  
    if ( (n <0) || (count <  n))
      return false;
      
    if (n == 0) {
      addFirst(o);
      // addFirst() increments count or does it???
      return true;
    }
    
    Node indexNode;
    int indexInt;
    
    for (indexInt=0, indexNode = head; 
         indexInt < (n-1); 
         indexInt++, indexNode = indexNode.getNext())
    ;
    
    Node newNode = new Node(indexNode, o, indexNode.getNext());
    indexNode.setNext (newNode);
    
    count++;
  
    // If successor
    if (newNode.getNext() != null)  
       newNode.getNext().setPrev(newNode);
    
    // Fix the tail     
    if (tail.getNext() != null)
      tail = tail.getNext();
     

    return true;  
  }
  
  /*
   * Nice, readable, but inefficient
  public boolean insertBefore(Object insertMe, Object beforeMe) {
  
    int position = indexOf(beforeMe);
    
    if (position < 0)
      return false;
      
    insertAtN (insertMe, position);
    // Count is incremented by insertAtN
  
    return true;
  }
  */
  
  public boolean insertBefore (Object insertMe, Object beforeMe) {
    Node index;
    
    if (count == 0) // protects index.getNext() in for loop below
      return false;
    
    for (index=head;
        ((index.getNext() != null) &&
        (!index.getNext().getItem().equals(beforeMe)));
        index=index.getNext())
    ;
    
    if (index.getNext() == null) 
      return false;  
      
    Node newNode = new Node(index, insertMe, index.getNext());
    
    index.setNext (newNode);
    
    if (newNode.getNext() != null)
      newNode.getNext().setPrev(newNode);
    count++;
    
    return true;  
  }
  
  
  public Object removeNth (int n) {
    Object saved;

    if ( (n < 0) || (n >= count))
      return null;

    if (n == 0) {
      saved = head.getItem();
      head = head.getNext();
      head.setPrev(null);
      count--;
      return saved;
    }

    Node indexNode;
    int i;
    
    for (i = 0, indexNode = head; 
         (i < (n - 1)); 
         i++, indexNode = indexNode.getNext())
    ;
  
    saved = indexNode.getNext().getItem();
  
    count--;

  
    if (tail == indexNode.getNext()) 
      tail = indexNode;

    indexNode.setNext(indexNode.getNext().getNext());
    
    if (indexNode.getNext() != null)
      indexNode.getNext().setPrev(indexNode);
  
    return saved;
  }  
  
  public Iterator iterator() {
    return new DLLIterator (head);
  }
       
}