CSC416 Foundation of Computer Science II


Lecture 2 - April 05, 2004

  1. Abstract Data Type
    1. Set of objects together with a set of operations
    2. e.g., lists, sets, graphs and their operations can be abstract datatypes
    3. Integers, reals, and booleans are primitive data types.
      • They have operations associated with them
      • (add, substract, multiply, divide)
      • The type int is an abstract data type in the sense that we can think about the qualities of an int apart from any real thing having that quality. In other words, we don't need to know how ints are represented or how the operations are implemented to be able to use them or reason about them.
    4. ADT: set
      • Can have operations union and find
    5. Java and ADTs
      1. Java is a language based around the creation of abstract data types. (Think of the class and object idea.)
      2. allows to hide implementation details
      3. operations can be performed by just calling the appropriate methode
      4. if detail of implemenation needs to be changed, it can be changed with no effect to the user because it is changed in the routines that perform the ADT operations
    6. Which operations?
      • Up to you
  2. The List ADT
    1. How we define a list
      1. A1, A2, A3, A4,..., AN
      2. Size of list is N
      3. list of seize 0 is the empty list
      4. Ai+1 follows or succeeds Ai
      5. Position is of element Ai in the list is i
      6. possible operations
        1. printList
        2. makeEmpty
        3. find
        4. insert
        5. remove
        6. findKth
        7. next
        8. previous
        9. which functions and what they do is up to the programmer
    2. Array Solution of a List
      1. estimate of the maximum size is necessary
      2. usually requires a high estimate, which wastes space - especially if list size is unknown
      3. time taken for different operations
        1. printList => linear
        2. find => linear
        3. findKth => constant
        4. insert => linear - have to move each element down one
        5. delete => linear - have to move each element up one
        6. building a list by successive inserts would take quadratic time
      4. arrays aren't usually used for lists because
        1. insert and delete too slow
        2. list size must be known in advance. (even a dynamically created list would need some estimate of a maximum size, and a dynamically created list would take even more processing time)
    3. Linked Lists
      1. General idea of a linked list
        1. Series of nodes .
                  +-------+   +-------+   +--------+
             +--->| 3 | o---->| 1 | o---->| 5 |NULL|
             |    +-------+   +-------+   +--------+
             |
           head
        2. Not necessarily adjacent in memory (as opposed to a list)
        3. Each node contains the element and a link to next node
        4. The last cell's next link references null
        5. printList and find(x)
          • start at the first node in the list and traverse the list by following the next links
          • linear time - same as array, but the constant in front of the actual time is going to be greater because of the extra steps in traversing to the next link
        6. findKth
          • has to traverse down the list and count each one
          • O(N) - linear
          • not as efficient as for the array
          • usually(in practice) when asking for the kth, you are actually asking for the next one, which can be computed in constant time
        7. remove
          • constant time O(1)
          • change one next link
          • e.g., before A2 pointed to A3, now it points to A4
                        +---------------+
                        |               |
                  +-----|-+   +-------+ |  +--------+
             +--->| 3 | o |   | 1 | o | +->| 5 |NULL|
             |    +-------+   +-------+    +--------+
             |
           head
        8. insert
          • have to make new node, and change two next links
          • linear time O(N)
                  +-------+   +-------+             +--------+
             +--->| 3 | o---->| 1 | o |          +->| 5 |NULL|
             |    +-------+   +-----|-+          |  +--------+
             |                      |  +-------+ |
           head                     +->| 2 | o---+
                                       +-------+
    4. Programming Issues
      1. How do you add to the front of the list?
      2. How do you delete from the front of the list?
      3. How do you prevent losing the list?
      4. How do we delete if we don't know who is pointing to a node?
    5. Solutions
      1. To the first 3 problems
        • Create a dummy or header node at the front of the list
        • header -> A1 -> ...
      2. To the delete problem
        • findPrevious method
        • if we want to delete the first element, findPrevious will return the header
        • it moves through each element of the list starting from the header until it finds the one before
        • this method is linear time, which unfortunately makes delete linear time
    6. Three Classes
      1. LinkedList
      2. ListNode
      3. LinkedListItr - position
    7. ListNode Code
      package DataStructures;
      
      class ListNode
      {
         // data: accessible by other classes in the package
         Object element;
         ListNode next;
      
         // Constructors
         ListNode (Object theElement)
         {
            this(theElement, null);
         }
      
         ListNode (Object theElement, ListNode n)
         {
            element = theElement;
            next = n;
         }
      }
      
    8. LinkedListItr
      • iterator class
      • provides methods to iterate through the list
      • keeps the current position by a reference to a ListNode
      • isPastEnd is true if the position is beyond the end of the list
      • retrieve returns the element in the current position
      • advance moves to the next element in the list
      • constructor requires the current node (can be the header)
                     +---+---+  +---+---+  +---+---+  +---+----+
                 +-->| 3 | o--->| 1 | o--->| 2 | o--->| 5 |NULL|
                 |   +---+---+  +---+---+  +---+---+  +---+----+
               +-|-+                       ^
        header | o |                       |
               +---+           +-----------|---+
                               |         +-|-+ |
                               | current | o | |
                               |         +---+ |
                               +---------------+
                            a LinkedListItr object
      • Code
        package DataStructures;
        
        public class LinkedListItr
        {
           //data fields
           ListNode current; // current position
        
           LinkedListItr( ListNode theNode)
           {
              current = theNode;
           }
         
           public boolean isPastEnd()
           {
              return current == null;
           }
        
           public Object retrieve()
           {
              return isPastEnd() ? null : current.element;
           }
        
           public void advance()
           {
              if( !isPastEnd() )
                 current = current.next;
           }
        }
        
    9. LinkedList
      1. Class LinkedList represents the actual list -- a linked list of ListNode objects.
      2. Weiss' implementation of LinkedList class contains a dummy header node :
                       +---+---+   +---+---+   +---+----+
                 +---->|   | o---->| 3 | o---->| 1 |NULL|
                 |     +---+---+   +---+---+   +---+----+
               +-|-+   dummy node   real data
        header | o |               starts from here
               +---+                         
        Header node is useful for eliminating cumbersome "special cases" when nodes are inserted/deleted at the front.
      3. LinkedList class also contains some operations/methods typically performed on a list.
      4. Code
        package DataStructures;
        
        public class LinkedList
        {
           // data
           private ListNode header;
        
           public LinkedList()
           {
              header = new ListNode(null);
           }
        
           public boolean isEmpty()
           {
              return header.next == null;
           }
        
           public void makeEmpty()
           {
              header.next = null;
           }
        
           public LinkedListItr zeroth()
           {
              return new LinkedListItr ( header );
           }
        
           public LinkedListItr first()
           {
              return new LinkedListItr ( header.next );
           }
        
        
           /** 
            * Return iterator corresponding to the first node
            * containning an item.
            * @param x the item to search for
            */
           public LinkedListItr find (Object x )
           {
              ListNode itr = header.next;
              while(itr != null && !itr.element.equals( x ) )
                 itr = itr.next;
              return new LinkedListItr (itr);   
           }
        
           /**
            * Remove the first occurrence of an item.
            * @param x the item to remove.
            */
           public void remove ( Object x )
           {
              LinkedListItr p = findPrevious(x);
              if (p. current.next != null )
                 p.current.next = p.current.next.next;
           }
        
           /**
            * Return iterator prior to the first node containing an item.
            * @param x the item to search for
            * @return the appropriate iterator if the item is found.
            * Otherwise the iterator corresponding th the last element in
            * the list is returned.
            */
           public LinkedListItr findPrevious (Object x )
           {
              ListNode itr = header;
              while(itr.next != null && !itr.next.element.equals(x))
                 itr = itr.next;
              return new LinkedListItr (itr);
           }
        
           /**
            * Insert after p
            * @param x the item to insert
            * @param p the position prior to the newly inserted item
            */
           public void insert (Object x, LinkedListItr p)
           {
              if (p != null && p.current != null)
                 p.current.next = new ListNode (x, p.current.next);
           }
        
        }
        
      5. makes a data field accessing the header node
      6. header node made by the constructor with a null element
      7. zeroth moves the current position to the head
      8. first moves the current position to the first element
      9. makeEmpty throws away the list by making the header linker null (remember automatic garbage collection)
      10. isEmpty checks if the header link is null (which implies there is no first element)
      11. find - returns the position of some element - takes advantage of && operation
      12. remove
        • removes the first occurance of x
        • does nothing if x is not in the list
        • we may want to update the code to return true if removed and false otherwise to aid the case where you want to delete all occurances
        • finds the previous element and has that elements next link point the the link after the one you are removing
    10. find previous is similar to find - searches until you find an object such that the next link points to the element you want to find the previous for
    11. insert
      1. pass in element to be inserted and where
      2. places it after position p (all this is arbitrary)
      3. (if placed it before would have to call findPrevious making insert O(N) and thus as bad as array)
    12. Running time:
      1. O(N): find, findPrevious, remove
      2. O(1): all other calls
    13. Printing the List
      1. (Code)
        //simple print method
        public static void printLinst(LinkedList theList)
        {
           if (theList.isEmpty() )
              System.out.print("Empty List");
           else
           {
              LinkedListItr itr = theList.first();
              for( ; !itr.isPastEnd(); itr.advance() )
                 System.out.print(itr.retrieve() + " ");  
           }
           System.out.println();
        }
        
      2. Can be implemented in any method since it only calls public methods
      3. moves to the first element, then moves forward as and retrieves item as long as not past the end of the list
    14. Do we need all three classes - why not two?
      1. i.e., have LinkedList hold the position
      2. position and list are separate objects
      3. allows list to be accessed in several places simultaneously
      4. (can have more than one position)
      5. could have a function remove that takes in two positions and removes between them
    15. Doubly Linked Lists
      1. add to the node an extra field that stores a link to the previous node
      2. allows to traverse link backwards.
      3. doubles the cost of insertions because more links to fix (but still O(1))
      4. makes findPrevious and remove O(1) as well
    16. Circularly Linked Lists
      1. last node has a link to the first
      2. slightly change some of the code
      3. good for circles or representing a circular network
    17. Examples
      1. Polynomial ADT - use linked list to represent simple variable polynomials
        1. holds values such as 4 + 3x + 2x2 + 5x3 + ...
        2. operations: addition, subtraction, multiplicaiton, differentiation
        3. Array Representation
          1. Array index represents the x term, and the value is the coefficient
          2. Code
            public class Polynomial
            {
            
               public static final int MAX_DEGREE = 100;
               private int coefArray[] = new int [MAX_DEGREE + 1];
               private int highPower = 0;
            
               public Polynomial()
               {
                  zeroPolynomial();
               }
            
               public void insertTerm (int coef, int exp )
               {
                  // ...
               }
            
               public void zeroPolynomial()
               {
                  for (int i = 0; i <= MAX_DEGREE; i++ )
                     coeffArray[i];
                  highPower = 0;
               }
            
               public Polynomial add( Polynomial rhs)
               {
                  Polynomial sum = new Polynomial();
                  sum.highPower = max(highPower, rhs.highPower);
                  for ( int i = sum.highPower; i >= 0; i-- )
                     sum.coeffArray[i] = coeffArray[i] + rhs.coeffArray[i];
                  return sum;
               }
            
               public Polynomial multiply( Polynomial rhs) throws Overflow
               {
                  Polynomial product = new Polynomial();
                  product.highPower = highPower + rhs.highPower;
                  if (product.highPower > MAX_DEGREE )
                     throw new Overflow;
                  for (int i = 0; i <= highPower; i++ )
                     for (int j = 0; j <= rhs.highPower; j++ )
                        product.coeffArray[i+j] += coeffArray[i] * rhs.coeffArray[j];
                  return product;
               }
            }
            
          3. not good for sparse polynomials: 10x1000 + 5x14 + 1
          4. have to know how large the polynomial is before hand - overestimate can waste a lot of space
        4. Singly Linked List Representation
          1. Each node contains to pieces of data: term and coefficient
          2. Code
            public class Literal
            {
               Various constructors (not shown)
               int coefficient;
               int exponent;
            }
            
            public class Polynomial
            {
               
               private LinkedList terms;
            
               public Polynomial()
               { ... }
            
               public void insertTerm(int coed, int exp)
               { ... }
            
               public void zeroPolynomial()
               { ... }
            
               public Polynomial add(Polynomial rhs)
               { ... }
            
               public Polynomial multiply ( Polynomial rhs )
               { ... }
            
               public void print()
               { ... }
            
            }
            
    18. Java LinkedList class
      • Java Collection has a class called LinkedList which implements a doubly-linked list.  The specification of this class is described in Sun's Java API on LinkedList class.
      • Small example of an application:
        import java.util.*;  // need to import
        public class JLTest
        {
          public static void main(String[] args)
          {
            LinkedList lst = new LinkedList();
            // addLast(obj)
            lst.addLast(new Character('A'));
            lst.addLast(new Character('B'));
            lst.addLast(new Character('C'));
            System.out.println(lst);  // [A, B, C]
            // add(index, obj)
            lst.add(0, new Character('X')); // 0-based index, insert at/before
            System.out.println(lst);  // [X, A, B, C]
            // remove(obj)
            boolean result = lst.remove(new Character('B'));
            System.out.println(result);  // true
            System.out.println(lst);     // [X, A, C]
            // remove(index)
            Character c = (Character) lst.remove(2);
            System.out.println(c);    // C
            System.out.println(lst);  // [X, A]
        
            // get(index)
            Character c2 = (Character) lst.get(1);
            System.out.println(c2);   // A
          }
        }
      • When you want to iterate through a Java LinkedList list, you use an object of (or which implements) Java ListIterator interface.  (You can use LinkedList's get instead, but it is O(n) every time it is called, thus it will be inefficient.)
        ListIterator it = lst.listIterator(0);  // 0-based index
        while (it.hasNext())
          System.out.println
           ("Element: " + it.next()); // advance it and returns element
                                      // which used be pointed
      • Comparing Linked Lists and ArrayList classes in Java

        Worst case complexity

        Operation LinkedList ArrayList
        add(obj) O(1) O(1)
        add(0,obj) O(1) O(n)
        add(index,obj) O(n) O(n)
        remove(0,obj) O(1) O(n)
        remove(index,obj) O(n) O(n)
        remove() (iterator) O(1) O(n)
        contains O(n) O(n)
        get O(n) O(1)
        set O(n) O(1)
      • Java ListIterator interface implements a method remove -- to remove a list node through iterator.  
        IMPORTANT NOTE:
        This method works somewhat counter-intuitively.
        LinkedList lst = new LinkedList();
        lst.addLast(new Character('A'));
        lst.addLast(new Character('B'));
        lst.addLast(new Character('C')); // lst is [A, B, C]
        
        ListIterator it2 = lst.listIterator(0);  // it points to A
        it2.next();               // it points to B
        
        it2.remove();             // call remove
        
        System.out.println(lst);  
          
       
    19. Cursor-implementation of Linked List
      Basic concepts
                 have node pool of all nodes, keep list of free ones
                 use pool indices in place of pointers;  0 == NULL
                data stored as collection of nodes: data and next without dynamic memory allocation
      
      • CursorNode

         
            // Basic node stored in a linked list -- cursor version
            // Note that this class is not accessible outside
            // of package DataStructures
        
            class CursorNode
            {
                    // Constructors
                CursorNode( Object theElement )
                {
                    this( theElement, 0 );
                }
        
                CursorNode( Object theElement, int n )
                {
                    element = theElement;
                    next    = n;
                }
        
                    // Friendly data; accessible by other package routines
                Object   element;
                int      next;
            }
        
      • CursorList

        // CursorList class
            //
            // CONSTRUCTION: with no initializer
            // Access is via CursorListItr class
            //
            // ******************PUBLIC OPERATIONS*********************
            // boolean isEmpty( )     --> Return true if empty; else false
            // void makeEmpty( )      --> Remove all items
            // CursorListItr zeroth( )--> Return position to prior to first
            // CursorListItr first( ) --> Return first position
            // void insert( x, p )    --> Insert x after current iterator position p
            // void remove( x )       --> Remove x
            // CursorListItr find( x )
            //                        --> Return position that views x
            // CursorListItr findPrevious( x )
            //                        --> Return position prior to x
            // ******************ERRORS********************************
            // No special errors
        
            /**
             * Linked list implementation of the list
             *    using a header node; cursor version.
             * Access to the list is via CursorListItr.
             * @author Mark Allen Weiss
             * @see CursorListItr
             */
            public class CursorList
            {
                private static int alloc( )
                {
                    int p = cursorSpace[ 0 ].next;
                    cursorSpace[ 0 ].next = cursorSpace[ p ].next;
                    if( p == 0 )
                        throw new OutOfMemoryError( );
                    return p;
                }
        
                private static void free( int p )
                {
                    cursorSpace[ p ].element = null;
                    cursorSpace[ p ].next = cursorSpace[ 0 ].next;
                    cursorSpace[ 0 ].next = p;
                }
        
                /**
                 * Construct the list.
                 */
                public CursorList( )
                {
                    header = alloc( );
                    cursorSpace[ header ].next = 0;
                }
        
                /**
                 * Test if the list is logically empty.
                 * @return true if empty, false otherwise.
                 */
                public boolean isEmpty( )
                {
                    return cursorSpace[ header ].next == 0;
                }
        
                /**
                 * Make the list logically empty.
                 */
                public void makeEmpty( )
                {
                    while( !isEmpty( ) )
                        remove( first( ).retrieve( ) );
                }
        
        
                /**
                 * Return an iterator representing the header node.
                 */
                public CursorListItr zeroth( )
                {
                    return new CursorListItr( header );
                }
        
                /**
                 * Return an iterator representing the first node in the list.
                 * This operation is valid for empty lists.
                 */
                public CursorListItr first( )
                {
                    return new CursorListItr( cursorSpace[ header ].next );
                }
        
                /**
                 * Insert after p.
                 * @param x the item to insert.
                 * @param p the position prior to the newly inserted item.
                 */
                public void insert( Object x, CursorListItr p )
                {
                    if( p != null && p.current != 0 )
                    {
                        int pos = p.current;
                        int tmp = alloc( );
        
                        cursorSpace[ tmp ].element = x;
                        cursorSpace[ tmp ].next = cursorSpace[ pos ].next;
                        cursorSpace[ pos ].next = tmp;
                    }
                }
        
                /**
                 * Return iterator corresponding to the first node containing an item.
                 * @param x the item to search for.
                 * @return an iterator; iterator isPastEnd if item is not found.
                 */
                public CursorListItr find( Object x )
                {
        /* 1*/      int itr = cursorSpace[ header ].next;
        
        /* 2*/      while( itr != 0 && !cursorSpace[ itr ].element.equals( x ) )
        /* 3*/          itr = cursorSpace[ itr ].next;
        
        /* 4*/      return new CursorListItr( itr );
                }
        
                /**
                 * Return iterator prior to the first node containing an item.
                 * @param x the item to search for.
                 * @return appropriate iterator if the item is found. Otherwise, the
                 * iterator corresponding to the last element in the list is returned.
                 */
                public CursorListItr findPrevious( Object x )
                {
        /* 1*/      int itr = header;
        
        /* 2*/      while( cursorSpace[ itr ].next != 0 &&
                          !cursorSpace[ cursorSpace[ itr ].next ].element.equals( x ) )
        /* 3*/          itr = cursorSpace[ itr ].next;
        
        /* 4*/      return new CursorListItr( itr );
                }
        
                /**
                 * Remove the first occurrence of an item.
                 * @param x the item to remove.
                 */
                public void remove( Object x )
                {
                    CursorListItr p = findPrevious( x );
                    int pos = p.current;
        
                    if( cursorSpace[ pos ].next != 0 )
                    {
                        int tmp = cursorSpace[ pos ].next;
                        cursorSpace[ pos ].next = cursorSpace[ tmp ].next;
                        free( tmp );
                    }
                }
        
                // Simple print method
                static public void printList( CursorList theList )
                {
                    if( theList.isEmpty( ) )
                        System.out.print( "Empty list" );
                    else
                    {
                        CursorListItr itr = theList.first( );
                        for( ; !itr.isPastEnd( ); itr.advance( ) )
                            System.out.print( itr.retrieve( ) + " " );
                    }
        
                    System.out.println( );
                }
        
                private int header;
                static CursorNode[ ] cursorSpace;
        
                private static final int SPACE_SIZE = 100;
        
                static
                {
                    cursorSpace = new CursorNode[ SPACE_SIZE ];
                    for( int i = 0; i < SPACE_SIZE; i++ )
                        cursorSpace[ i ] = new CursorNode( null, i + 1 );
                    cursorSpace[ SPACE_SIZE - 1 ].next = 0;
                } 
        
                public static void main( String [ ] args )
                {
                    CursorList    theList = new CursorList( );
                    CursorListItr theItr;
                    int i;
        
                    theItr = theList.zeroth( );
                    printList( theList );
        
                    for( i = 0; i < 10; i++ )
                    {
                        theList.insert( new MyInteger( i ), theItr );
                        printList( theList );
                        theItr.advance( );
                    }
        
                    for( i = 0; i < 10; i += 2 )
                        theList.remove( new MyInteger( i ) );
        
                    for( i = 0; i < 10; i++ )
                        if( ( i % 2 == 0 ) != ( theList.find( new MyInteger( i ) ).isPastEnd( ) ) )
                            System.out.println( "Find fails!" );
        
                    System.out.println( "Finished deletions" );
                    printList( theList );
                }
        
            }
        
        
      • CursorListItr

        // CursorListItr class; maintains "current position"
            //
            // CONSTRUCTION: Package friendly only, with a CursorNode
            //
            // ******************PUBLIC OPERATIONS*********************
            // void advance( )        --> Advance
            // boolean isPastEnd( )   --> True if at valid position in list
            // Object retrieve        --> Return item in current position
        
            /**
             * Linked list implementation of the list iterator
             *    using a header node; cursor version.
             * @author Mark Allen Weiss
             * @see CursorList
             */
            public class CursorListItr
            {
                /**
                 * Construct the list iterator
                 * @param theNode any node in the linked list.
                 */
                CursorListItr( int theNode )
                {
                    current = theNode;
                }
        
                /**
                 * Test if the current position is past the end of the list.
                 * @return true if the current position is null-equivalent.
                 */
                public boolean isPastEnd( )
                {
                    return current == 0;
                }
        
                /**
                 * Return the item stored in the current position.
                 * @return the stored item or null if the current position
                 * is not in the list.
                 */
                public Object retrieve( )
                {
                    return isPastEnd( ) ? null : CursorList.cursorSpace[ current ].element;
                }
        
                /**
                 * Advance the current position to the next node in the list.
                 * If the current position is null, then do nothing.
                 */
                public void advance( )
                {
                    if( !isPastEnd( ) )
                        current = CursorList.cursorSpace[ current ].next;
                }
        
                int current;    // Current position
            }