CS62 - Spring 2010 - Lecture 10

  • Discuss extra credit on the assignment 3

  • Iterators: if you're still fuzzy about iterators
       - I've posted a solution to the lab. Take a look at it if you had problems.
       - take a look at section 9.8 in the book after today's lecture and make sure that makes sense to you

  • A few things
       - keep up with the reading!
       - read the assignments thoroughly before starting
       - we don't explicitly do designs in this class, but think about how the pieces fit together
       - been a bit lax about the practice problems because we've been covering odd topics, but we'll start again next week!
       - Sunday office hours will be moved an hour and a half later to: 9:30 - 11:30

  • lists (http://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html)
       - "An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list."
       - Design an interface for list. What methods should you support?
          - add to the end
          - add at a particular index
          - contains
          - get
          - set
          - isEmpty
          - size
          - remove
          - subList
       - The Vector class implements the List interface. Let's look at the runtime of the various methods:
          - what operations are performed quickly by the Vector class?
             - add to the end
             - get
             - set
             - size

  • Linked lists
       - the Vector class is built on top of an underlying array
          - because of this, the get and set operations are efficient
          - what operations are slow?
             - inserting an element at a particular index
             - removing an element
             - searching
       - where are we headed now?
          - there's no one best data structure
          - depending on what operations are important, one data structure may be more appropriate than another
          - we're going to start looking at different data structures that are good at different things
       - Today: linked lists are a recursive data structure
          - the data structure is built out of nodes
          - a node stores both a data item as well as a reference to another node
          - draw a picture and compare and contrast a picture of an array (or a Vector)
       - how could we do this using a class?
       - look at the Node class in LinkedList code
          - two things that we keep track of (with private instance variables)
             - private E data: the data that we're actually storing in the node
             - private Node<E> nextElement: a link to the next node in our linked list
          - we have methods that allow us to access and manipulate the data within the node
          - as well as methods that allow us to traverse and manipulate the links
       - how would I build a linked list with N data items
          - start by creating node (node1) with some data
          - create a new node (node2) with some other data and point it's next element at node1
          - create a new node (nod3) with some more data and point it's next element at nod2
          - etc, up through nodeN
          - in the end, we'll have nodeN->nodeN-1->...->node3->node2->node1
       - if someone gave us the first node, how would we traverse the linked list?
          Node<E> finger = head_of_the_list;

          while( finger != null ){
             finger = finger.next();
          }
       - We want to build a linked list data structure that supports similar operations to Vector. How would we do it?
          - we'll just store a link the head of the linked list
          - all of our operations will start at the head and then modify/traverse the list appropriately
       - A few operations (look at LinkedList code)
          - public void addFirst(E value)
             - we need to associate head with a new value
             - and set the "next" element for the new head to be the old head
             - this is accomplished at the same time, using the Node constructor
          - public E removeFirst()
             - save the value of head so we can return it
             - set head to be head.next()
             - return the value of the original head
          - public boolean contains(E value)
             - start at the head and traverse through the nodes
             - we can just keep a Node<E> reference and update it with .next() to move to the next node
             - stop when we either find it or we run off the end
          - Could we do this recursively?
             - we'd need a helper function: take in the value AND the current node
             - base case: if the current node is null, return false
             - recursive case:
                - if it's the current node, return it
                - otherwise, recurse on one node down the list
          - public void clear()
             - just need to set head = null and we're done
          - A few more interesting ones to try:
             - public void remove(E value)
             - public int indexOf(E value)
             - public E get(int index)
             - public addLast(E value)

  • What does the linked list buy us?
       - add and remove at the front of the list in constant time!
       - what are the run-times of the other operations?
          - get and set are now linear time operations
          - size is linear time. Could we do better?
             - just keep another instance variable that is the number of elements

  • Making it even better
       - add just appends one thing on to the end of the list, it seems unfortunate that this takes linear time. How could we make it faster?
          - keep a tail reference
          - how does this help us?
             public add(E value){
                if( head == null ){
                   head = new Node<E>(value);
                   tail = head;
                }else{
                   tail.setNext(new Node<E>(value);
                   tail = tail.next();
                }
             }
          - what's the running time? Now it's constant!
       - what are the downsides to using a tail pointer?
          - more memory (though only marginally)
          - makes methods slightly more complicated
       - Can we get rid of it?
          - what would happen if we connected the next reference of the last element to the head of the list?

  • Circularly linked lists
       - In a circularly linked list, all nodes have a next reference creating one big loop
       - Do we still need both a head and a tail reference?
          - the head reference is just tail.next()
             - note that even when there is only one element, we'll still have a loop (with just one element) and therefore tail.next() will still reference the head of the list
       - Did we lose any functionality from before?
          - no, we can still add things and remove things to the head of the list in constant time
          - and add things to the tail of the list in constant time
       - What if we want to remove items from the end of the list?
          - ideally, you'd like to just splice it off
          - the problem, though, is that you need to know what the previous node is to do this, so you'd have to search through the list, which would still take linear time

  • Doubly linked list
       - the linked list we've seen so far is called a singly linked list since we just have a single forward link
       - in a doubly linked list our Node class has both a next reference as well as a prev reference
       - How does this help us?
          - for simplicity, we'll say it's not a circularly linked list
          - given the tail, we can "splice" out that node:
             tail = tail.prev();
             tail.setNext(null);
          - there are a few more details to deal with the case when we have < 2 nodes, but it's still a constant time operation now!
       - What if someone gives you a Node, call it d, and says that they want you delete it from the doubly linked list?
          d.prev().setNext() = d.next();
          d.next().setPrev() = d.prev();
          
          - we just splice it out
          - again, there are a few more details to deal with with respect to the head, etc., but it's now a constant time operation!
          - When could this happen?
             - if we had an iterator over the list and we implemented the "remove()" method