CSC 416 Lecture Notes Week 1 -- part 1
Introduction
Review of Math and Java Background



  1. Math review
    1. Exponentiation
      XAXB = XA+B

      XA / XB = XA-B

      (XA)B = XAB

      XN + XN = 2XN != X2N

      2N + 2N = 2N+1
    2. Logarithms
      1. Basic definition:
        XA = B iff logX B = A
      2. In computer science, logarithms are almost always base 2, which means the fundamental definition we care about is
        log 2N = N
      3. Useful theorems are:
        logA B = logC B / logC A , for A > 1, B,C > 0

        log AB = log A + log B , for A,B > 0

        log(AB) = B log A , for A,B > 0

        log X < X < e X, for all X > 0

        The first of these is useful for computing log2 on a calculator that only has log10 or loge (ln).
    3. Series

      N

      S 2i = 2N+1 - 1

      i=0



      N

      S Ai = (AN+1 - 1) / (A - 1)

      i=0



      N

      S i = N (N+1) / 2

      i=1
    4. Modular arithmetic
      1. Basic definition:
        A mod N = the remainder after division of A by N.
      2. Useful theorems are:
        • if A = B mod N then A + C = B + C mod N
        • if A = B mod N then AD = BD mod N

        Question : Is the following statement also true ?

        if AD = BD mod N then A = B mod N

      Answer : False (A = 2 , B = 4 , D = 3 with N = 6 ) .

    5. Proofs
      1. By induction:
        1. The goal is to prove that a statement is true for all N, where N is the measure of something.
        2. Most typically in computer science induction proofs, N will be the number of elements in some data structure or the number of steps that an algorithm takes to execute.
        3. An induction proof has two steps:
          1. The proof of a base case, typically when N = 0 or 1.
          2. The proof of the inductive step, where we assume what we're trying to prove is true for N, and prove it's true for N + 1.
        4. Example : Prove that the Fibonacci numbers, F0 = 1, F1=1, F2=2, F3=3, F4=5, ... ,
          Fi=Fi-1 + Fi-2, satisfy Fi < (5/3)i .
      2. Proof by counter example.
        1. This is used to refute a statement that is claimed true for all N.
        2. It works by providing a single value of N > 0 where the statement is false.
        3. Example : To prove Fk < k2 is false by showing that F11 = 144 > 112 .
      3. Proof by contradiction.
        1. Assume that what we're trying to prove is false.
        2. Then show that this assumption leads to an erroneous result.
        3. Therefore, the assumption of falseness cannot be correct, which means what we're trying to prove must be true.
        4. Example : Prove that there are infinite many prime numbers

          Assume the statement is not true, that is,the number of primes is finite,say, N of them . We can list them out as P1,P2 ... PN. Then we claim that we can construct another ,the (N+1)-th, prime number as follows :
          PN+1 = P1*P2*..PN + 1 .
          Clearly PN+1 is prime since it is not divisible by any prime P1 through PN . This contradict to the the assumption that there are only finite primes.

  2. A brief introduction to recursion.
    1. The use of recursion will be key to a number of the algorithms we will study this quarter.
    2. The basic structure of a recursive algorithm is the following:
      1. Base case: Define what the algorithm computes in the simplest possible case.
      2. Recursive case: Define what the algorithm computes in a single step through the algorithm, such that the single step can be applied recursively until the base case is reached.
    3. Consider the following recursive algorithm for summing the elements in a list of numbers:
      1. Base case: If there are no elements in the list, compute the sum as zero.
      2. Recursive case: Compute the sum by adding the first element of the list to the recursive sum of the rest of the list.
    4. Here is a Java method that implements this recursive summing algorithm:
      /**
       * Return the sum of the elements in the given numbers array, starting at
       * the given index position.  If the position is past the end of the array,
       * return 0.
       */
      public int sumArray(int[] numbers, int position) {
      
          /*
           * Base case: Return 0 if the position is past the end of the array.
           */
          if (position >= numbers.length)
              return 0;
      
          /*
           * Recursive step: Add the element at the current position to the
           * recursive sum of the elements at position+1 to the end.
           */
          else
              return numbers[position] + sumArray(numbers, position+1);
      }
      
    5. This example of recursion is very simple, and could actually be accomplished more efficiently with a for-loop.
      1. The example does however illustrate the basic idea of a recursive algorithm.
      2. We will definitely see more sophisticated uses of recursion as the quarter progresses, particularly when we get to the study of tree data structures.
    6. Another example: Compute Fibonacci numbers Fi
      1. recursive version :

        
                int Fibonacci(int n) {
                   if ( n==0 || n==1 ) return 1 ;  // F(0) = F(1) = 1 
                   return Fibonacci(n-1) + Fibonacci(n-2) ; // F(n) = F(n-1) + F(n-2)
                }
        
               

      2. non-recursive version -- iteration

                   int Fibonacci(int n ) {
                     int F0 = 1 , F1 = 1 ;
                     int F2 ;
                     if ( n == 0 || n == 1 ) return 1 ;
                     for ( int i = 2 ; i <= n ; i ++ ) {
                       F2 = F1 + F0 ;
                       F0 = F1 ;    
                       F1 = F2 ;
                     }
                     return F1 ;
                  }
          

      3. Which version you prefer ?
        1. Recursive version has less number of line of code and simply translate the recursive definition of Fibonacci number into Java code.
        2. You will find out that the recursive version will take much longer running time (or even run out of resource) to compute F(n) even for relatively small n .
        3. In general, it is harder to debug the recursive algorithms.
        4. So,the real-time performance , in general, recursive is not as efficient as non-recursive in Java.

  3. Generic objects in Java data structures.
    1. An important concept we will use in our study of data structures is that of generic objects, and in particular generic collections.
    2. A generic collection is a data structure that can hold values of any type, rather than a specific type.
    3. Different programming languages provide different means to define generic collections.
      1. In C++, we use templates to define generic structures.
      2. In Java, the key to defining generic collections is the use of generic class and interface definitions, including the most generic Object class.
    4. To motivate the use of generics, consider the following Java method to find the largest element in an array of integers.
      /**
       * Return the largest element in the given array of integers.  Return
       * Integer.MIN_VALUE if the array is null or empty.
       */
      public int findLargest(int[] numbers) {
      
          int largestValue;                       // Largest value so far
          int i;                                  // Array index
      
          /*
           * Return Integer.MIN_VALUE if the array is null or empty.
           */
          if ((numbers == null) || (numbers.length == 0)) {
              return Integer.MIN_VALUE;
          }
      
          /*
           * Initialize the largest value to the zeroth array element, then loop
           * through the remaining elements, reassigning the largest value
           * whenever one of the elements is greater than the largest so far.
           */
          for (largestValue = numbers[0], i = 1; i < numbers.length; i++) {
              if (largestValue < numbers[i]) {
                  largestValue = numbers[i];
              }
          }
          return largestValue;
      }
      
    5. The problem with this implementation is that it only works for arrays that contain integer values.
      1. Suppose we wanted to find the largest element in an array of some other type of values, say an array of arrays?
      2. If we try to send findLargest such an array, the Java compiler will complain that an array of arrays is not compatible with an array of ints.
    6. The solution to the problem is to define a findLargest method that takes an array of more generic elements than just ints; here's the Java code for such a method:
      /**
       * Return the largest element in the given array of comparable objects.
       * Return null if the array is null or empty.
       */
      public Object findLargest(Comparable[] objects) {
      
          Comparable largestValue;                // Largest value so far
          int i;                                  // Array index
      
          /*
           * Return null if the array is null or empty.
           */
          if ((objects == null) || (objects.length == 0)) {
              return null;
          }
      
          /*
           * Initialize the largest value to the zeroth array element, then loop
           * through the remaining elements, reassigning the largest value
           * whenever one of the elements is greater than the largest so far.
           */
          for (largestValue = objects[0], i = 1; i < objects.length; i++) {
              if (largestValue.compareTo(objects[i]) < 0) {
                  largestValue = objects[i];
              }
          }
          return largestValue;
      }
      
    7. The key differences between this new version of findLargest and the original integer-only version are the following:
      1. The return value is of type Object, which is the most generic type of all Java classes.
        1. Object is at the root of the Java class hierarchy.
        2. Every Java class has Object as a superclass.
      2. The input to findLargest is an array of type Comparable.
        1. Comparable is a Java interface that requires its extending classes to implement the compareTo method.
        2. compareTo returns a negative integer, zero, or a positive integer, based on whether the object it is applied to is less than, equal to, or greater than the input parameter object.
    8. We will see continued uses of Object, Comparable, and other generic Java classes as we discuss various data structures.

  4. Java exceptions.
    1. Exceptions in Java are objects that are used to indicate that a method has failed in some way.
    2. Exceptions are tied to the Java try, catch, and throw statements.
    3. An upcoming example introduces the use of exceptions.
    4. We will study the subject of exception handling further as we introduce new data structures.

  5. Java input/output.
    1. The Java language has been designed to write programs that talk to the user primarily through a GUI -- graphical user interface.
      1. As a result, input/output on a standard terminal interface is not all that well-supported in Java.
      2. You are undoubtedly aware of this from your programming work in CSC 211 and 212.
    2. The following programs provide a quick review of terminal i/o in Java, as well as the use of exception handling.
      1. As the comment indicates, it is a test driver program for the recursiveSum method presented earlier in the notes.

        import java.io.*;
        import java.util.*;
        
        /****
         *
         * Class BasicRecursionTest illustrates the use of a very basic recursive
         * function to sum the elements in an array.  The elements are read from stdin
         * and stored in the array.  The resulting sum is printed to stdout.
         *                                                                          
         */
        
        public class BasicRecursionTest {
        
            /**
             * Call the recursiveSum method and print the result to stdout.
             */
            public static void main(String args[]) throws IOException {
        
                BasicRecursion basic;           // The class with the method
                BufferedReader reader;          // Input stream reader
                StringTokenizer toker;          // Input stream tokenizer
                final int NUM_ELEMENTS = 10;    // Number of inputs allowed
                int[] numbers                   // Input array
                    = new int[NUM_ELEMENTS];
                int i;                          // Working array index
        
                /*
                 * Instantiate a BasicRecursion class object.
                 */
                basic = new BasicRecursion();
        
                /*
                 * Prompt for the input.
                 */
                System.out.print("0utput up to " +
                    Integer.toString(NUM_ELEMENTS) + " numbers: ");
        
                /*
                 * Initialize the input reader and tokenizer.
                 */
                reader = new BufferedReader(new InputStreamReader(System.in));
                toker = new StringTokenizer(reader.readLine());
        
                /*
                 * Read each input from the terminal.
                 */
                for (i = 0; toker.hasMoreElements() && i < 10; ) {
        
                    /*
                     * Store the next input in the array, ignoring any non-numeric
                     * input.
                     */
                    try {
                        numbers[i] = Integer.parseInt(toker.nextToken());
                    }
                    catch (NumberFormatException e) {
                        continue;
                    }
        
                    /*
                     * Increment the input index if the read was a legal number.
                     */
                    i++;
        
                }
        
                /*
                 * Output the array sum to stdout.
                 */
                System.out.print("The sum is: ");
                System.out.println(basic.sumArray(numbers, 0));
                System.out.println();
        
            }
        
        }
        

      2. Another Example : Display the content of the given file to the console

        import java.io.FileReader;
        import java.io.BufferedReader;
        import java.io.IOException;
        
        public class ListFiles
        {
            public static void main( String [ ] args )
            {
                if( args.length == 0 )
                    System.out.println( "No files specified" );
                for( int i = 0; i < args.length; i++ )
                    listFile( args[ i ] );
            }
        
            public static void listFile( String fileName )
            {
                FileReader theFile;
                BufferedReader fileIn = null;
                String oneLine;
        
                System.out.println( "FILE: " + fileName );
                try
                {
                    theFile = new FileReader( fileName );
                    fileIn  = new BufferedReader( theFile );
                    while( ( oneLine = fileIn.readLine( ) ) != null )
                        System.out.println( oneLine );
                }
                catch( IOException e )
                  {  System.out.println( e ); }
                finally
                {
                    // Close the stream
                    try
                    {
                        if(fileIn != null )
                            fileIn.close( );
                    }
                    catch( IOException e )
                      { }
                }
            }
        }
        

      3. Example -- Use of StringTokenizer

        import java.io.*;
        import java.util.*;
        
        public class UsingStringTokenizer
            {
            public static void main(String[] args) throws Exception   
              {  BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        
                 System.out.println("Enter a few integers");        
                 String oneLine = br.readLine(); 
                 StringTokenizer  str = new StringTokenizer(oneLine);        
                 while(str.hasMoreTokens())
                         System.out.println(str.nextToken()); 
               // another way to use StringTokenizer
        
                    StringTokenizer str2= new StringTokenizer(br.readLine( ));
        
                    int numTokens = str2.countTokens( );
        
                    int x = Integer.parseInt(str2.nextToken());  //  if you know the next token will be an integer          
        
             }  // end main
        }//end class UsingStringTokenizer
         
      4. Example : FileCopy - adding line number , use of command-line argument

            import java.io.*;
        
            public class NumberLines
            {
                public static void main( String [ ] args )
                {
                    PrintWriter   fileOut = null;
                    BufferedReader fileIn = null;
        
                    try
                    {
                        if( args.length == 1 )
                            fileOut = new PrintWriter( System.out );
                        else if( args.length == 0 || args.length > 2 )
                            throw new Exception( "Usage: java NumberLines sourceFile destFile" );
                        else if( args[ 0 ].equals( args[ 1 ] ) )
                            throw new Exception( "Cannot copy to self" );
                        else
                            fileOut = new PrintWriter( new BufferedWriter(
                                                 new FileWriter( args[ 1 ] ) ) );
        
                        fileIn  = new BufferedReader( new FileReader( args[ 0 ] ) );
        
                        String oneLine;
                        int lineNum = 0;
        
                        while( ( oneLine = fileIn.readLine( ) ) != null )
                        {
                            fileOut.print( ++lineNum + "\t");
                            fileOut.println( oneLine );
                        }
                    }
                    catch( Exception e )
                      { System.out.println( e ); }
        
                    try
                    {
                        if( fileIn != null )
                            fileIn.close( );
                        if( fileOut != null )
                            fileOut.close( );
                    }
                    catch( IOException e )
                      { }
                }
            }
        

      5. Another Simple I/O example :

        import java.io.InputStreamReader;
        import java.io.BufferedReader;
        import java.io.IOException;
        
        import java.util.StringTokenizer;
        
        public class MaxTest
        {
            public static void main( String [ ] args )
            {
                BufferedReader in = new BufferedReader( new
                                     InputStreamReader( System.in ) );
                
                String oneLine;
                StringTokenizer str;
                int x;
                int y;
        
                System.out.println( "Enter 2 ints on one line: " );
                try
                {
                    oneLine = in.readLine( );
                    if( oneLine == null )
                        return;
                        
                    str = new StringTokenizer( oneLine );
                    if( str.countTokens( ) != 2 )
                    {
                        System.out.println( "Error: need two ints" );
                        return;
                    }
                    x = Integer.parseInt( str.nextToken( ) );
                    y = Integer.parseInt( str.nextToken( ) );
                    System.out.println( "Max: " + Math.max( x, y ) );
                }
                catch( IOException e )
                  { System.err.println( "Unexpected IO error" ); }
                catch( NumberFormatException e )
                  { System.err.println( "Error: need two ints" ); }
            }
        }