CMPS 144
Recursive solutions to array problems: Three examples

Problem 1: Array sum

Develop a Java method that, given an array a[] of integers, computes the sum of the elements in a[]. (Note that, in Java, the index range of an array a is 0..a.length-1. We use a[] as an abbreviation for a[0..a.length-1].) The specification is as follows:

      /* pre:  none
         post: value returned is sum of elements in a[]
      */
      public static int arraySum(int[] a) { ... }

An obvious iterative solution (to replace "..." in the specification above) is as follows:

         int sum = 0;

         // loop invariant: sum = sum of elements in a[0..i-1]

         for (int i=0; i != a.length; i++) {
            sum = sum + a[i];
         }

         // assertion 1: i = a.length  &  sum = sum of elements in a[0..i-1]
         // assertion 2: sum = sum of elements in a[0..a.length-1]

         return sum; 

Of course, assertion 1 is simply the conjunction of the negation of the loop guard and the loop invariant. Assertion 2 follows from 1 and tells us that the next statement returns the correct value to the caller.

Let us now consider how we might solve this problem recursively. One idea is this: the sum of the elements in an empty array is zero. The sum of the elements in a nonempty array can be obtained by adding the last element to the sum of all the previous elements.

To state it a little more precisely: for any natural number n not greater than a.length we have

  arraySum( a[0..n-1] ) =  { 0                               if n=0 
                           { arraySum( a[0..n-2] ) + a[n-1]  otherwise

This suggests the following recursive solution:

      public static int arraySum(int[] a) { 

         int n = a.length;

         if (n == 0) 
            { return 0; }
         else
            { return arraySum( a[0..n-2] ) + a[n-1]; }
      }  

Unfortunately, in Java there is no notation (such as a[0..n-2]) by which to refer to a segment of an array. Thus, the above is not syntactically correct. To get the effect of passing an array segment as a parameter, we let the method have three parameters: the array itself together with int parameters low and high indicating, respectively, the left and right boundaries of the segment of interest. (For reasons of convenience, we use the convention that high - 1, rather than high itself, is the location of the last element in the segment.) We get the following "auxiliary" method:

      /* pre:  0 <= low <= high <= a.length
         post: value returned is sum of elements in a[low .. high-1]
      */
      public static int arraySumAux(int[] a, int low, int high) { 

         if (low == high) 
            { return 0; }
         else  // low < high
            { return arraySumAux(a, low, high-1) + a[high-1]; }
      }

Now we can write the original method as follows:

      /* pre:  none
         post: value returned is sum of elements in a[0..a.length-1]
      */
      public static int arraySum(int[] a)

         { return arraySumAux(a, 0, a.length); }

Problem 2: String Reversal

Develop a Java method that, given a String s, returns the reverse of s.

Let c0c1c2...cn be a string, where each ci is a character. Then its reverse is cncn-1...c2c1c0

Notice that the first character of the reverse is the last character of the original and that what follows it is the reverse of the string obtained by chopping off the last character of the original string. Combining this with the observation that the reverse of the empty string is itself, we surmise that

     reverse(s) = { s                        if s has length 0
                  { a + reverse(s')          otherwise

            where + denotes concatenation, s' denotes 
            s with its last character chopped off, and a
            denotes the last character of s.

Indeed, this recursive definition of reverse corresponds to our intended meaning. We shall not prove this here, but, just for illustration, we demonstrate the application of the function to the string "abcde":

 reverse("abcde") = 'e' + reverse("abcd") 
                  = 'e' + ('d' + reverse("abc"))
                  = 'e' + ('d' + ('c' + reverse("ab")))
                  = 'e' + ('d' + ('c' + ('b' + reverse("a"))))
                  = 'e' + ('d' + ('c' + ('b' + ('a' + reverse("")))))
                  = 'e' + ('d' + ('c' + ('b' + ('a' + ""))))
                  = 'e' + ('d' + ('c' + ('b' + "a")))
                  = 'e' + ('d' + ('c' + "ba"))
                  = 'e' + ('d' + "cba")
                  = 'e' + "dcba"
                  = "edcba"  

Each of the first five lines follows from the recursive case of the definition of reverse; the sixth line follows from the base case; the remaining lines follow from the meaning of concatenation.

In order to express the above in Java, we must be able to refer to the last character in a String object s as well as to the string obtained by chopping off the last character in a String object s. Assuming that n is the length of s, these are given by the expressions s.charAt(n-1) and s.substring(0, n-1). We must also be able to express concatenation. But Java gives us this with the + operator (which is why we chose to use it above). This leads to the following method:

   /* pre:  none
      post: object returned is a String that is the reverse of s
   */ 
   public static String reverseOf( String s ) {

      if (s.length == 0)
         { return s; }
      else {
         int n = s.length;
         return s.charAt(n-1) + reverseOf( s.substring(0, n-1) );
      }
   }  

Problem 3: Binary Search

Using N as an abbreviation for a.length and a[m..n] < x as an abbreviation for "a[i] < x for all i satisfying m<=i<=n" (and similarly for >= in place of <), develop a Java method with the following specification:

    /* pre:  elements in a[] are in ascending order
       post: a[] is unchanged and the value returned (call it k) satisfies
             0 <= k <= N &&
             a[0..k-1] < x  &&
             a[k..N-1] >= x,
    */
    public static int binSearch( int[] a, int x ) 
That is, the method, given an int x and ascending-ordered array a[] of ints, returns k such that every value in a[0..k-1] is less than x and no value in a[k..N-1] is less than x. In picture form, we want
    0                     k                       N
   +---------------------+-----------------------+
 a |     all < x         |      all >= x         |
   +---------------------+-----------------------+

Idea: Due to the fact that the elements of a[] are in ascending order, we can search for x using the classic binary search method discussed in class, in which we successively halve the search space until it has been reduced to one so small that the result can be computed directly (i.e., without further halving).

As we observed in Problem 1, there is no expression in Java by which to refer to an array segment. Hence, we introduce an auxiliary method with int parameters low and high (in addition to the array parameter) to indicate the boundaries of the array segment serving as the remaining search space. Let us agree that for a[low..high-1] to be the remaining search space requires that a[0..low-1] < x and a[high..N-1] >= x. In picture form:

    0             low             high           N
   +-------------+---------------+--------------+
 a |   all < x   |       ?       |   all >= x   |
   +-------------+---------------+--------------+

If low == high, the ?-section of the array is empty, so the desired result is high (or, equivalently, low). Otherwise, we compute mid to be the average of low and high and then determine in which of the two segments, a[low..mid] or a[mid+1..high], the correct answer must lie. Either way, the answer may be obtained recursively, because what remains to be done is to search the appropriate segment:

    /* pre:  Letting N = a.length,
          0 <= low <= high <= N  &&
          a[0..N-1] in ascending order &&
          a[0..low-1] < x  &&
          a[high..N-1] >= x
       post: a[] is unchanged and value returned (call it k) satisfies 
             a[0..k-1] < x  &&  a[k..N-1] >= x
    */
    public static int binSearchAux( int[] a, int low, int high, int x ) {

       if (low == high)
          { return high; }
       else {
          int mid = (low + high) / 2;
          // assertion: low <= mid < high
          if ( a[mid] >= x )
             { return binSearchAux(a, low, mid, x); } 
          else // a[mid] < x 
             { return binSearchAux(a, mid+1, high, x); } 
       }
    }  

The body of the original method, whose purpose was to search an entire array, can be written as an invocation of the above auxiliary method:

    /* pre:  elements in a[] are in ascending order
       post: returns k satisfying a[0..k-1] < x  &&  a[k..N-1] >= x
    */
    public static int binSearch( int[] a, int x )

       { return binSearchAux(a, 0, a.length, x); }