introduction to computer science recursive array programming recursive sorting algorithms unit 16
Post on 22-Dec-2015
252 views
TRANSCRIPT
Introduction to Computer Science
• Recursive Array Programming
• Recursive Sorting Algorithms
Unit 16Unit 16
16- 2
Recursive Array Programming
Recursive function definitions assume that a function works for a smaller value.
With arrays, "a smaller value" means a shorter array, i.e., a subarray, contiguous elements from the original array
We'll define a recursive function over an array by using the same function over a subarray, and a base case
Subscripts will mark the lower and upper bounds of the subarrays
16- 3
Subarrays
myArray
[0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
Base case
1 through 92 through 9
3 through 94 through 9
5 through 9
6 through 9
7 through 98 through 9
9 through 9
16- 4
Example: Recursively find sum of array elements, A[lo] to A[hi]
• Assume sum( ) properly returns sum of elements for a smaller array of doubles
• Then we could write:
double sum (double[ ] A, int lo, int hi) {return ( A[lo] + sum(A, lo + 1, hi) );
}
• But we're not done; what's the base case?
16- 5
Base Case is when Subarray is Empty, hi is less than lo
double sum (double[ ] A, int lo, int hi) {if (hi < lo)
return 0.0;else
return ( A[lo] + sum(A, lo + 1, hi) );}
• Yes, we could have defined this using(hi == lo) as the base case…
16- 6
Recursive Sorting Algorithms
• We can use this same idea of recursive functions over subarrays to rewrite our sorting algorithms
• Let's see how this works for selection sort, insertion sort, and then some new sorting algorithms
16- 7
Selection Sort (REVIEW)
18 2235 97 84 55 61 10 47
starting order:
18223597 84 55 61 10 47
search through array, find largest value, exchange withfirst array value:
1822 3597 84 55 61 10 47
search through rest of array, find second-largest value,exchange with second array value:
16- 8
Selection Sort Pseudocode (REVIEW)
for every “first” component in the arrayfind the largest component in the array;exchange it with the “first” component
18 223597 84 5561 10 47
18 223597 84 5561 10 47
18 22 3597 84 5561 1047
18223597 84 5561 1047
18223597 84 5561 1047
18223597 84 5561 1047
16- 9
The Selection Sort Java Code (REVIEW)
void select (int[ ] data) { // Uses selection sort to order an array of integers. int first, current, largest, temp;
for (first = 0; first < data.length - 1; first++) {largest = first;for (current = first + 1; current < data.length; current+
+) { if ( data[current] > data[largest] ) largest =
current;}// Postcondition: largest is index of largest item
// from first..end of arrayif (largest != first) { // We have to make a swap
temp = data[largest];data[largest] = data[first]; // Make the swapdata[first] = temp;
}}
} // select
16- 10
Recursive Selection Sort
• Let's say we want to sort an array A from index "lo" to index "hi", largest to smallest
• We place the largest element in A[lo]
• Then recursively sort the rest of the array from A[lo + 1] to A[hi]
• The base case is the one-element subarray when lo equals hi
16- 11
The Recursive Selection Sort Java Code
void selectionSort(int[ ] data, int lo, int hi) {// data[0]…data[lo-1] contain the largest values in data, // in descending order
if (lo < hi) { //subarray has more than one elementswap(data, lo, findMaximum(data, lo, hi));selectionSort(data, lo+1, hi);
}}
int findMaximum(int[ ] data, int lo, int hi) {if (lo == hi) return lo;else { int locationOfMax = findMaximum(data, lo+1, hi); if (data[lo] > data[locationOfMax]) return lo; else return locationOfMax;}
}
16- 12
Code for swap( )
• The above version of selectionSort( ) is much less efficient than the iterative version; we show it just as an example of recursive array programming
void swap(int[ ] data, int first, int second) { int temp;
temp = data[first];data[first] = data[second];data[second] = temp;
}
16- 13
What Does the Outside World See?
• We can use overloading, and provide a one-argument version of selectionSort( ) for outside use. No one needs to know whether it was implemented using recursion or iteration:
void selectionSort(int[ ] data) {selectionSort(data, 0, data.length - 1);
}
• The internal version should be private
16- 14
Insertion Sort
18 2235 97 84 55 61 10 47
starting order:
18 2235 97 84 55 61 10 47
move through the array, keeping the left side ordered;when we find the 35, we have to slide the 18 over to make room:
182235 97 84 55 61 10 47
continue moving through the array, always keeping the leftside ordered, and sliding values over as necessary to do so:
18 slid over
18 slid over
16- 15
Continue the Insertion Process
18223597 84 55 61 10 47
the left side of the array is always sorted, but may requireone or more components to be slid over to make room:
35, 22, and 18 slid over
18223597 84 55 61 10 4735, 22, and 18 slid over
18223597 84 55 61 10 4735, 22, and 18 slid over
16- 16
Continue the Insertion Process
18223597 84 5561 10 4755, 35, 22, and 18 slid over
18223597 84 5561 10 47nothing slides over
18223597 84 5561 104735, 22, 18, and 10 slid over
16- 17
The Insertion Sort Java Code(Review)
void insert (int[ ] data) { // Uses insertion sort to order an array of integers.int newest, current, newItem;boolean seeking;for (newest = 1; newest < data.length; newest++) {
seeking = true;current = newest;newItem = data[newest];while (seeking) { // seeking newItem's new position on left if (data[current - 1] < newItem) {
data[current] = data[current -1]; //slide value rightcurrent- -; seeking = (current > 0);
} else seeking = false;} // while// Postcondition: newItem belongs in data[current]data[current] = newItem;
} // newest for} // insert
16- 18
How Do We Do Insertion Sort Recursively?
• How can the ability to sort an array of length n-1 be used to sort an array of length n?
• Answer: sort the array of length n-1, then insert the nth element in the proper place
• That is: to sort subarray A[0]…A[hi], sort A[0]…A[hi-1], then insert A[hi] into that smaller subarray
• insertInOrder(A, hi, x) will be used to insert x into subarray A[0]…A[hi-1]
16- 19
Recursive Insertion Sort
void insertionSort(int[ ] data, int hi) {
// Sort data[0]…data[hi]
if (hi > 0) {
insertionSort(data, hi-1);
insertInOrder(data, hi, data[hi]);
}
}
16- 20
What About insertInOrder( )?
• We'll define it recursively
• To insert x into subarray A[0]…A[hi-1]:
– If x ≤A[hi-1], then put x into A[hi]
– If x > A[hi-1], then move A[hi-1] into A[hi]
and insert x into subarray A[0]…A[hi-2]
16- 21
Recursive insertInOrder( )
void insertInOrder(int[ ] data, int hi, int x) { // Insert x into data[0]…data[hi-1], filling // in data[hi] in the process.
// data[0]…data[hi-1] are sorted.if ( (hi == 0) || (data[hi-1] >= x) )
data[hi] = x;else {
data[hi] = data[hi-1];insertInOrder(data, hi-1, x);
}}
16- 22
What Does the Outside World See (again)?
• We can use overloading, and provide a one-argument version of insertionSort( ) for outside use. No one needs to know whether it was implemented using recursion or iteration:
public void insertionSort(int[ ] data) {insertionSort(data, data.length - 1);
}
• The internal version should be private
16- 23
More Recursive Sorting: Quicksort
• Quicksort is an O(n2) algorithm in the worst case, but its running time is usually proportional to n log2 (n); it is the method of choice for many sorting jobs
• We’ll first look at the intuition behind the algorithm: take an array
V O N I C AE Rarrange it so the small values are on the left half and all the big values are on the right half:
V O NI C AE R
16- 24
Quicksort Intuition
• Then, do it again:
VO NIC A E Rand again:
VO NICA E RThe divide-and-conquer strategy will, in general, take log2n steps to move the A into position. The intuition is that, doing this for n elements, the whole algorithm will take O(n log2 n) steps.
16- 25
What Quicksort is really doing
1. Partition array a into smaller elements and larger ones – smaller ones from a[0]…a[m-1] (not necessarily in order), larger ones in positions a[m+1]…a[length-1] (not necessarily in order), and the “middle” element in a[m]. So a:
5 3 11 7 1927 12 18might be partitioned (with m=4) as:
5 3 117 192712 18pivotsmaller than pivot larger than pivot
16- 26
quicksort(), next 2 steps
2. Recursively sort a[0]…a[m-1]. Our a becomes:
53 117 192712 18
3. Recursively sort a[m+1]…a[length-1]. Our a becomes:
53 117 19 271218
pivot
pivot
16- 27
quicksort( )
private void quicksort(double[] a, int lo, int hi) {int m;
if (hi > lo+1) { // there are at least 3 elements// so sort recursively
m = partition(a, lo, hi);quicksort(a, lo, m-1);quicksort(a, m+1, hi);
}else // the base case…
…}
16- 28
The base case…
• We have the base case in this recursion when the subarray a[lo]…a[hi] contains zero elements (lo==hi+1), one element (lo==hi), or two elements (lo==hi-1).
• With no elements, we ignore it
• With one element, it’s already sorted
• With two elements, we just swap them:// 0, 1, or 2 elements, so sort directlyif (hi == lo+1 && a[lo] > a[hi])
swap(a, lo, hi);
16- 29
quicksort( )
private void quicksort(double[] a, int lo, int hi) {int m;
if (hi > lo+1) { // there are at least 3 elements// so sort recursively
m = partition(a, lo, hi);quicksort(a, lo, m-1);quicksort(a, m+1, hi);
}else // 0, 1, or 2 elements, so sort directly
if (hi == lo+1 && a[lo] > a[hi])swap(a, lo, hi);
}
16- 30
The outside world’s version
• As usual, we overload quicksort( ) and provide a public version that “hides” the last two arguments:
public void quicksort(double[] a) {quicksort(a, 0, a.length-1);
}
16- 31
Now, all we need is partition( )
int partition(double[] a, int lo, int hi) {// Choose middle element among a[lo]…a[hi],// and move other elements so that a[lo]…a[m-1]// are all less than a[m] and a[m+1]…a[hi] are// all greater than a[m]//// m is returned to the caller
…
}
16- 32
Are you feeling lucky?
• Now, this would work great if we knew the median value in the array segment, and could choose it as the pivot (i.e., know which small values go left and which large values go right). But we can’t know the median value without actually sorting the array!
• So instead, we somehow pick a pivot and hope that it is near median.
• If the pivot is the worst choice (each time), the algorithm becomes O(n2). If we are roughly dividing the subarrays in half each time, we get an O(nlog2n) algorithm.
16- 33
How to pick the median value
• There are many techniques for doing this. In practice, one good way of choosing the pivot is to take the median of three elements, specifically a[lo+1], a[(lo+hi)/2], and a[hi]
• We’ll choose their median using the method medianLocation( )
16- 34
medianLocation( )
int medianLocation(double[] a,int i, int j, int k)
{if (a[i] <= a[j])
if (a[j] <= a[k])return j;
else if (a[i] <= a[k])return k;
else return i;else // a[j] < a[i]
if (a[i] <= a[k])return i;
else if (a[j] <= a[k])return k;
else return j;}
16- 35
The partitioning process
• From the three red elements, we choose the median, a[hi]
8 1 2 6 34 9 10 5 7lo+1 hi(lo+hi)/2
• We swap the median with a[lo], and start the partitioning process on the rest:
7 1 2 6 34 9 10 5 8lo+1 hi(lo+hi)/2
16- 36
The partitioning process
• We shuffle around elements from a[lo+1] to a[hi] so that all elements less than the pivot (a[lo]) appear to all the elements greater than the pivot
• Until we are done, we have no way of knowing how many elements are less and how many elements are greater
7 1 2 6 104 5 3 8 9lo+1 hi(lo+hi)/2 m
16- 37
The partitioning process
• m is the largest subscript that contains a value less than the pivot
• We have discovered (in our example) that m = 6
• We then swap a[m] with a[lo], placing the pivot in its rightful position, in a[m], then continue to sort the left and right subarrays recursively
7 1 2 6 104 5 3 8 9lo+1 hi(lo+hi)/2 m
6 1 2 7 104 5 3 8 9lo+1 hi(lo+hi)/2 mlo
pivot
16- 38
How partition( ) works
• We will use a 3-argument version:int partition(double[ ] a, int lo, int hi)
and a 4-argument version:int partition(double[ ] a, int lo, int hi, double pivot)
• The 3-argument version moves the pivot element into a[lo], calls the 4-argument partition to shuffle the elements in the subarray a[lo+1]…a[hi], then swaps a[lo] into a[m] and returns m
16- 39
3-argument partition( )
int partition(double[] a, int lo, int hi) {// Choose middle element among a[lo]…a[hi],// and move other elements so that a[lo]…a[m-1]// are all less than a[m] and a[m+1]…a[hi] are// all greater than a[m]//// m is returned to the callerswap(a, lo, medianLocation(a, lo+1, hi, (lo+hi)/2));int m = partition(a, lo+1, hi, a[lo]);swap(a, lo, m);return m;
}
16- 40
How the 4-argument partition( ) works
• The 4-argument version does the main work, recursively calling itself on subarrays
• We of course assume partition( ) works on any smaller array…
• If the first element of the array is less than or equal to pivot, it’s already in the right place, just call partition( ) recursively on the rest:
if (a[lo] <= pivot) // a[lo] in correct halfreturn partition(a, lo+1, hi, pivot);
(this is the “current” lo, not the lo of the whole array)
16- 41
How the 4-argument partition( ) works
• If a[lo] > pivot, then a[lo] belongs in the upper half of the subarray, and we swap it with a[hi]
• We still don’t know where the new a[lo] value should go, so we partition recursively on the subarray that includes a[lo] but not a[hi]
if (a[lo] <= pivot) // a[lo] in correct halfreturn partition(a, lo+1, hi, pivot);
else { // a[lo] in wrong halfswap(a, lo, hi);return partition(a, lo, hi-1, pivot);
}
16- 42
How the 4-argument partition( ) works
• The base case is the one element subarray, when lo==hi. Is the one element “small” or “large”?
• If it is small (less than the pivot), then it is at the middle point m (and we can swap it with the pivot)
• Otherwise, it is just above the middle point (and we want the pivot swapped with the element just below it)
16- 43
4-argument partition( )
int partition(double[] a,int lo, int hi, double pivot) {
if (hi == lo)if (a[lo] < pivot)
return lo;else
return lo-1;else if (a[lo] <= pivot) // a[lo] in correct half
return partition(a, lo+1, hi, pivot);else { // a[lo] in wrong half
swap(a, lo, hi);return partition(a, lo, hi-1, pivot);
}}
16- 44
Example of partition
• The starting array:
4 2 15 3 6lo hi
• Choose the median from among:
4 2 15 3 6lo hi(lo+hi)/2lo+1
• Swap the median with a[lo]:
3 2 15 4 6pivot
16- 45
Example of partition
• Now, partition the subarray (not counting pivot); a is now the new subarray…
3 2 15 4 6lo hi
• a[lo] > pivot, so swap it with a[hi], and continue with the partition:
3 2 51 4 6lo hi
pivot
pivot
16- 46
Example of partition
• a[lo] is now less than pivot, so we leave it and continue with the partition:
3 2 51 4 6lo hi
• Now a[lo] is greater than pivot, so we swap it with a[hi] and continue with the partition:
3 2 51 6 4lo hi
pivot
pivot
16- 47
Example of partition
• a[lo] is again greater than pivot, so we swap it with a[hi] and continue with the partition:
3 6 51 2 4lo hipivot
• a[lo] is less than the pivot, so lo (i.e., index 2) is returned by the 4-argument partition( ); the 3-argument partition then swaps the pivot and the middle element
3 6 512 4pivot
Now we’re ready to recursively quicksort the left and right subarrays
16- 48
quicksort( )
private void quicksort(double[] a, int lo, int hi) {int m;
if (hi > lo+1) { // there are at least 3 elements// so sort recursively
m = partition(a, lo, hi);quicksort(a, lo, m-1);quicksort(a, m+1, hi);
}else // 0, 1, or 2 elements, so sort directly
if (hi == lo+1 && A[lo] > A[hi])swap(a, lo, hi);
}
16- 49
Performance Comparison
• Quicksort:
– Best case: O(nlog2n)
– Worst case: O(n2) – when the pivot is always the second-largest or second-smallest element (since medianLocation won’t let us choose the smallest or largest)
– Average case over all possible arrangements of n array elements: O(nlog2n)
• Selection Sort and Insertion Sort
– Average case: O(n2)