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:
|
An obvious iterative solution (to replace "..." in the specification above) is as follows:
|
Of course, assertion #1 is simply the conjunction of the loop invariant (which we believe to be true after every loop interation, including the last) and the negation of the loop guard (which we know to be true when the loop terminates). Assertion #2 follows from Assertion #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) ) = { | 0 | if n = 0 |
arraySum( a[0..n-1) ) + a[n-1] | otherwise |
This suggests the following recursive solution:
|
Unfortunately, in Java there is no notation (such as a[0..n-1)) 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 the specified left boundary is inclusive and the specified right boundary is exclusive, which is to say that the array segment specified includes location low and extends up to, but not including, location high.
We get the following "auxiliary" method:
|
Now we can write the original method as follows:
|
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 zero |
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:
|
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:
|
That is, the method, given an int x and ascending-ordered array a[] of int values, returns k such that every value in a[0..k) is less than x and every value in a[k..N) is greater than or equal to 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 the discussion of a 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) to be the remaining search space requires that a[0..low) < x and x ≤ a[high..N). 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 low (or, equivalently, high). Otherwise, we compute mid to be an integer halfway between 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:
|
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:
|