/** pre: exponent >= 0 post - Returns baseexponent **/ public int fastPower(int base, int exponent) { if (exponent == 0) return 1 else if (exponent%2 == 1) // exponent is odd return base * fastPower(base, exponent-1) else // exponent is even return fastPower(base * base, exponent / 2) }
Show FastPower(base,exp) = baseexp.
By induction on size of exp:
Base case: exp = 0, FastPower(base,0) = 1 = base0 -- correct!
Suppose FastPower(base,exp) = baseexp for all exp < n.
Show FastPower(base,n) = basen
Two cases:
n is odd: FastPower(base,n) = base * FastPower(base, n-1)Done with proof of correctness!= base * basen-1 (by induction)
= basen
n is even: FastPower(base,n) = FastPower(base*base,n / 2)
= (base2)(n / 2) (by induction)
= basen (since n is even)
protected void recInsSort(int last, Comparable[] elts){ if (last > 0){ recInsSort(last-1, elts); // Sort elts[0..last-1] int posn= last-1; // index where last shd be inserted // Search for first elt (from rear) <= elts[last] while (posn >= 0 && elts[last].lessThan(elts[posn])) posn--; posn++; // insert elts[index] at posn // move elts[posn .. last-1] to put in elts[last] Comparable tempElt = elts[last]; for (int moveIndex = last-1; moveIndex >= posn; moveIndex--) elts[moveIndex+1] = elts[moveIndex]; // insert element into proper posn elts[posn] = tempElt; // now elts[0..last] are in order } }
Clearly works for recInsSort(0, elts) -- array of size 1.
Suppose recInsSort(k, elts) correctly sorts elts[0..k] for k < n.
Show for recInsSort(n, elts).
Algorithm first calls recInsSort(n-1, elts).
By induction correctly
sorts elts[0..n-1].
Next find first posn (from rear) where elts[posn].lessThan(elts[n]).
Update posn = posn+1, so elts[posn] >= elts[n]
Moves each of elts[posn..last-1] one step to right, and inserts value of elts[last] in elts[posn].
Now elts[posn+1..last] >= elts[posn] > elts[posn-1] and elts[0..posn-1] are sorted.
Therefore elts[0..last] is sorted.
Because while loop may quit early, insertion sort only uses half as many comparisons (on average) than selection sort. Thus usually twice as fast (but same "O" complexity).
/** POST -- elementArray is sorted into non-decreasing order **/ public void sort(Comparable[] elementArray) { recMergeSort(0,elementArray.length -1,elementArray); } /** pre: first, last are legal indices of elementArray post: elementArray[firstIndex..last] is sorted in non-decreasing order **/ protected void recMergeSort(int first, int last, Comparable[] elementArray) { int middle = (first+last)/2; // middle index of array if (last - first > 0) // more than 1 elt { // Sort first half of list recMergeSort(first,middleIndex,elementArray); // Sort second half of list recMergeSort(middleIndex+1,last,elementArray); // Merge two halves mergeRuns(first,middleIndex,last,elementArray); } }Easy to show recMergeSort is correct if mergeRuns is.
Method mergeRuns is where all the work takes place. Note that you can't merge the halves in place. Hence we need an auxiliary array to copy into.
Note that mergeRuns is not recursive!
/** PRE -- sortArray[first..middle] and sortArray[middle+1..last] are sorted, and each range is non-empty. POST -- sortArray[first..last] is sorted **/ protected void mergeRuns (int first, int middle, int last, Comparable[] sortArray) { int elementCount = last-first+1; // # elts in array // temp array to hold elts to be merged Comparable[] tempArray = new Comparable[elementCount]; // copy elts of sortArray into tempArray in preparation // for merging for (int index=0; index < elementCount; index++) tempArray[index] = sortArray[first+index]; int outIndex = first; // posn written to in outArray int run1 = 0; // index of first, second runs int run2 = middle-first+1; int endRun1 = middle-first; // end of 1st, 2nd runs int endRun2 = last-first; // merge runs until one of them is exhausted while (run1 <= endRun1 && run2 <= endRun2) { if (tempArray[run1].lessThan(tempArray[run2])) { // if elt from run1 is smaller add it to sortArray sortArray[outIndex] = tempArray[run1]; run1++; } else { // add elt from run2 to sortArray sortArray[outIndex] = tempArray[run2]; run2++; } outIndex++; } // while // Out of elts from one run, but other may have elts // add remaining elements from run1 if any left while (run1 <= endRun1) { sortArray[outIndex] = tempArray[run1]; outIndex++; run1++; } // add remaining elements from run2 if any left while (run2 <= endRun2) { sortArray[outIndex] = tempArray[run2]; outIndex++; run2++; } }It is easy to convince yourself that mergeRuns is correct. (A formal proof of correctness of iterative algorithms is actually harder than for recursive programs!)
It is also easy to see that if the portion of the array under consideration has k elements (i.e., k = last-first+1), then the complexity of mergeRuns is O(k):
If only look at comparisons then clear that every comparison (i.e., call to lessThan) in the if statement in the while loop results in an element being copied into sortArray.
In the worst case, you run out of all elts in one run when there is only 1 element left in the other run: k-1 comparisons, giving O(k)
If count copies of elements, then also O(k) since k copies in copying sortArray into tempArray, and then k more copies in putting elts back (in order) into sortArray.
Can use this to determine the complexity of recMergeSort.
Claim
complexity is O(n log n) for sort of n elements.
Easiest to prove this if n = 2m for some m.
Prove by induction on m that sort of n = 2m elements takes <= n log n = 2m * m compares.
Base case: m=0, so n = 1. Don't do anything, so 0 compares <= 20 * 0.
Suppose true for m-1 and show for m.
recMergeSort of n = 2m elements proceeds by doing recMergeSort of two lists of size n / 2 = 2m-1, and then call of mergeRuns on list of size n = 2m.
Therefore,
#(compares) <= 2m-1 * (m-1) + 2m-1 * (m-1) + 2m = 2*(2m-1 * (m-1)) + 2m = 2m * (m-1) + 2m = 2m * ((m-1) + 1) = 2m * mTherefore #(compares) <= 2m * m = n log n
End of proof.
Thus if n = 2m for some m, #(compares) <= n log n to do recursiveMergeSort
It is not hard to show that a similar bound holds for n not a power of 2.
Therefore O(n log n) compares. Same for number of copies.
Can cut down number of copies significantly if merge back and forth between two arrays rather than copy and then merge.
See efficient (but more complex) iterative version in MergeSort.java.