Linked List II

The public and private keywords

  • private method or field: invisible & inaccessible to other classes.
  • Why use it?
    • To provide protection from other classes
    • To ensure that you can improve the implementation of a class without causing other classes that depend on it to fail

Example: EvilTamperer tries to get around the error checking code of the Date class by fiddling with the internals of a Date object.

  public class Date {                  |  public class EvilTamperer {
    private int day;                   |    public void tamper() {
    private int month;                 |      Date d = new Date(1, 1, 2006);
                                       |
    private void setMonth(int m) {     |      d.day = 100;    // Foiled!!
      month = m;                       |      d.setMonth(0);  // Foiled again!!
    }                                  |    }
                                       |  }
    public Date(int month, int day) {  |
      [Implementation with             |
       error-checking code here.]      |
    }
  }

However, javac won't compile EvilTamperer.

Important definitions

  • The interface of a class = a set of prototypes for public methods + descriptions of the method's behaviors
  • An Abstract Data Type (ADT) is a class that has a well-defined interface, but its implementation details are firmly hidden from other classes. (so that you can change implementation without jeopardizing the programs that depend on it)
  • An invariant is a fact about a data structure that is always true. (e.g. A Data object always stores a valid date.) Enforced by allowing access only through method calls.
    • When to use private / getters, setters? Depending on whether there are invariants that need to be enforced.
    • Not all classes are ADTs! Some classes are data storage units; no invariants; fields can be public.

The SList ADT

Third advantage: it enforces 2 invariants.

  1. "size" is always correct.
  2. A list is never circularly linked; there is always a tail node whose "next" reference is null.

Both goals accomplished by making sure that only SList methods can change the lists. SList ensures that

  1. The fields of SList (head & size) are "private"
  2. No method of SList returns an SListNode

Doubly-linked lists

Inserting/deleting at front of list is easy, but at the end of list takes a long time.

  class DListNode {                    |  class DList {
    Object item;                       |    private DListNode head;
    DListNode next;                    |    private DListNode tail;
    DListNode prev;                    |  }
  }                                    |



           -------------      -------------      -------------
           |       item|      |       item|      |       item|
    head   |      -----|      |      -----|      |      -----|   tail
    -----  |----- | 4 ||      |----- | 1 ||      |----- | 8 ||  -----
    | . +->|| X | -----|<-----++-. | -----|<-----++-. | -----|<-+-. |
    -----  |----- -----|      |----- -----|      |----- -----|  -----
           |prev  | .-++----->|prev  | .-++----->|prev  | X ||
           |      -----|      |      -----|      |      -----|
           |       next|      |       next|      |       next|
           -------------      -------------      -------------

Insert & delete items at both ends in constant running time.

Remove the tail node if there are at lease two items.

tail.prev.next = null;
tail = tail.prev;

Need a special case for a DList with no items and also a special case for a DList with one item.

Another implementation

Sentinel - A special node that does not represent an item.

  class DList {
    private DListNode head;
    private int size;
  }

  // no null pointer in the prev/next field, making less special cases.


                          sentinel
                          -------------    -----
                          |       item|<---+-. |
          --------------->|      -----|    -----
          |               |prev  | X ||     head
          |               |----- -----|
          |               || .-+------+-----------------
          |               |----- -----|                |
          |      ---------+------+-. ||                |
          |      |        |  next-----|<---------------+-----
          |      |        -------------                |    |
          |      v                                     v    |
       ---+---------      -------------      -------------  |
       |  |    item|      |       item|      |       item|  |
       |  |   -----|      |      -----|      |      -----|  |
       |--+-- | 4 ||      |----- | 1 ||      |----- | 8 ||  |
       || . | -----|<-----++-. | -----|<-----++-. | -----|  |
       |----- -----|      |----- -----|      |----- -----|  |
       |prev  | .-++----->|prev  | .-++----->|prev  | .-++---
       |      -----|      |      -----|      |      -----|
       |       next|      |       next|      |       next|
       -------------      -------------      -------------

DList invariants (with sentinel)

  1. For any DList, d.head != null.
  2. For any DListNode, x.prev != null.
  3. For any DListNode, x.next != null.
  4. For any DListNode, if x.next == y, then y.prev == x.
  5. For any DListNode, if x.prev == y, then y.next == x.
  6. A DList "size" variable is # of DListNodes. (not counting sentinel, accessible from sentinel by sequence of next access)

Empty DList: Sentinel's prev & next fields points to itself.

Remove the last item from a DList.

public void removeBack(){
  if (head.prev != head) {
    head.prev = head.prev.prev;
    head.prev.next = head;
    size--;
  }
}

Debugging - Don't need to check the invariants in the implementation of the class. One good way to debug a program is to figure out what are the invariants are, write code to check the invariants, and check them when you're debugging. (write code that runs in the debug mode)

results matching ""

    No results matching ""