Title: Recursive Array Programming
1Introduction to Computer Science
Unit 16
- Recursive Array Programming
- Recursive Sorting Algorithms
2Recursive 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
3Subarrays
myArray
0
1
2
3
4
5
6
7
8
9
1 through 9
2 through 9
3 through 9
4 through 9
5 through 9
6 through 9
7 through 9
8 through 9
9 through 9
Base case
4Example Recursively find sum of array elements,
Alo to Ahi
- Assume sum( ) properly returns sum of elements
for a smaller array of doubles - Then we could writedouble sum (double A,
int lo, int hi) return ( Alo sum(A, lo
1, hi) ) - But we're not done what's the base case?
5Base Case is when Subarray is Empty, hi is less
than lo
- double sum (double A, int lo, int hi) if
(hi lt lo) return 0.0 else return ( Alo
sum(A, lo 1, hi) ) - Yes, we could have defined this using(hi lo)
as the base case
6Recursive 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
7Selection Sort (REVIEW)
starting order
18
22
35
97
84
55
61
10
47
search through array, find largest value,
exchange with first array value
18
84
22
55
61
10
47
35
97
search through rest of array, find second-largest
value, exchange with second array value
18
35
84
55
22
61
10
47
97
8Selection Sort Pseudocode (REVIEW)
- for every first component in the array
- find the largest component in the array
- exchange it with the first component
9The 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 lt data.length - 1 first) largest
first for (current first 1 current lt
data.length current) if ( datacurrent
gt datalargest ) largest current //
Postcondition largest is index of largest item
// from first..end of array if (largest !
first) // We have to make a swap temp
datalargest datalargest datafirst //
Make the swap datafirst temp // select
10Recursive 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 Alo
- Then recursively sort the rest of the array from
Alo 1 to Ahi - The base case is the one-element subarray when lo
equals hi
11The Recursive Selection Sort Java Code
void selectionSort(int data, int lo, int hi)
// data0datalo-1 contain the largest
values in data, // in descending order if (lo
lt hi) //subarray has more than one
element swap(data, lo, findMaximum(data, lo,
hi)) selectionSort(data, lo1, hi) int
findMaximum(int data, int lo, int hi) if
(lo hi) return lo else int
locationOfMax findMaximum(data, lo1, hi)
if (datalo gt datalocationOfMax) return lo
else return locationOfMax
12Code for swap( )
void swap(int data, int first, int second)
int temp temp datafirst datafirst
datasecond datasecond temp
- The above version of selectionSort( ) is much
less efficient than the iterative version we
show it just as an example of recursive array
programming
13What 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 iterationvoid
selectionSort(int data) selectionSort(data,
0, data.length - 1) - The internal version should be private
14Insertion Sort
starting order
18
22
35
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
35
18
97
84
22
55
61
10
47
18 slid over
continue moving through the array, always keeping
the left side ordered, and sliding values over as
necessary to do so
18
22
35
97
84
55
61
10
47
18 slid over
15Continue the Insertion Process
the left side of the array is always sorted, but
may require one or more components to be slid
over to make room
18
22
35
97
84
55
61
10
47
35, 22, and 18 slid over
18
22
35
97
84
55
61
10
47
35, 22, and 18 slid over
18
22
35
97
84
55
61
10
47
35, 22, and 18 slid over
16Continue the Insertion Process
61
18
22
35
97
84
55
10
47
55, 35, 22, and 18 slid over
61
18
22
35
97
84
55
10
47
nothing slides over
61
47
18
22
35
97
84
55
10
35, 22, 18, and 10 slid over
17The 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 lt data.length newest) seeking
true current newest newItem
datanewest while (seeking) // seeking
newItem's new position on left if
(datacurrent - 1 lt newItem) datacurrent
datacurrent -1 //slide value right current- -
seeking (current gt 0) else
seeking false // while // Postcondition
newItem belongs in datacurrent datacurrent
newItem // newest for // insert
18How 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 A0Ahi, sort
A0Ahi-1, then insert Ahi into that smaller
subarray - insertInOrder(A, hi, x) will be used to insert x
into subarray A0Ahi-1
19Recursive Insertion Sort
- void insertionSort(int data, int hi)
- // Sort data0datahi if (hi gt 0)
- insertionSort(data, hi-1)
- insertInOrder(data, hi, datahi)
-
-
20What About insertInOrder( )?
- We'll define it recursively
- To insert x into subarray A0Ahi-1
- If x Ahi-1, then put x into Ahi
- If x gt Ahi-1, then move Ahi-1 into Ahi and
insert x into subarray A0Ahi-2
21Recursive insertInOrder( )
void insertInOrder(int data, int hi, int x)
// Insert x into data0datahi-1,
filling // in datahi in the process. //
data0datahi-1 are sorted. if ( (hi 0)
(datahi-1 gt x) ) datahi x else
datahi datahi-1 insertInOrder(data,
hi-1, x)
22What 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 iterationpublic
void insertionSort(int data)
insertionSort(data, data.length - 1) - The internal version should be private
23More 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 - Well first look at the intuition behind the
algorithm take an array
V
O
N
I
C
A
E
R
arrange it so the small values are on the left
half and all the big values are on the right half
O
N
I
C
A
E
R
V
24Quicksort Intuition
O
N
I
C
A
E
R
V
and again
R
O
N
C
V
I
A
E
The 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.
25What Quicksort is really doing
- 1. Partition array a into smaller elements and
larger ones smaller ones from a0am-1 (not
necessarily in order), larger ones in positions
am1alength-1 (not necessarily in order),
and the middle element in am. So a
11
5
3
7
19
27
12
18
might be partitioned (with m4) as
5
7
19
3
11
12
18
27
smaller than pivot
larger than pivot
pivot
26quicksort(), next 2 steps
- 2. Recursively sort a0am-1. Our a becomes
5
3
7
19
11
12
18
27
pivot
3. Recursively sort am1alength-1. Our a
becomes
5
3
7
19
27
11
12
18
pivot
27quicksort( )
private void quicksort(double a, int lo, int
hi) int m if (hi gt lo1) // there are at
least 3 elements // so sort recursively m
partition(a, lo, hi) quicksort(a, lo,
m-1) quicksort(a, m1, hi) else // the
base case
28The base case
- We have the base case in this recursion when the
subarray aloahi contains zero elements
(lohi1), one element (lohi), or two elements
(lohi-1). - With no elements, we ignore it
- With one element, its already sorted
- With two elements, we just swap them
// 0, 1, or 2 elements, so sort directlyif (hi
lo1 alo gt ahi) swap(a, lo, hi)
29quicksort( )
private void quicksort(double a, int lo, int
hi) int m if (hi gt lo1) // there are at
least 3 elements // so sort recursively m
partition(a, lo, hi) quicksort(a, lo,
m-1) quicksort(a, m1, hi) else // 0, 1,
or 2 elements, so sort directly if (hi lo1
alo gt ahi) swap(a, lo, hi)
30The outside worlds 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)
31Now, all we need is partition( )
int partition(double a, int lo, int hi) //
Choose middle element among aloahi, // and
move other elements so that aloam-1 // are
all less than am and am1ahi are // all
greater than am // // m is returned to the
caller
32Are 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
cant 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.
33How 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 alo1, a(lohi)/2, and ahi - Well choose their median using the method
medianLocation( )
34medianLocation( )
int medianLocation(double a, int i, int j,
int k) if (ai lt aj) if (aj lt
ak) return j else if (ai lt
ak) return k else return i else // aj
lt ai if (ai lt ak) return i else if
(aj lt ak) return k else return j
35The partitioning process
- From the three red elements, we choose the
median, ahi
2
3
5
7
8
1
6
4
9
10
lo1
hi
(lohi)/2
- We swap the median with alo, and start the
partitioning process on the rest
2
3
5
8
7
1
6
4
9
10
lo1
hi
(lohi)/2
36The partitioning process
- We shuffle around elements from alo1 to ahi
so that all elements less than the pivot (alo)
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
2
10
8
9
7
1
6
4
5
3
lo1
hi
(lohi)/2
m
37The partitioning process
2
10
8
9
7
1
6
4
5
3
lo1
hi
(lohi)/2
m
- 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 am with alo, placing the pivot
in its rightful position, in am, then continue
to sort the left and right subarrays recursively
2
10
8
9
6
1
7
4
5
3
lo1
hi
(lohi)/2
m
lo
pivot
38How partition( ) works
- We will use a 3-argument versionint
partition(double a, int lo, int hi)and a
4-argument versionint partition(double a,
int lo, int hi, double pivot) - The 3-argument version moves the pivot element
into alo, calls the 4-argument partition to
shuffle the elements in the subarray
alo1ahi, then swaps alo into am and
returns m
393-argument partition( )
int partition(double a, int lo, int hi) //
Choose middle element among aloahi, // and
move other elements so that aloam-1 // are
all less than am and am1ahi are // all
greater than am // // m is returned to the
callerswap(a, lo, medianLocation(a, lo1, hi,
(lohi)/2)) int m partition(a, lo1, hi,
alo) swap(a, lo, m)return m
40How 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, its already in the right place,
just call partition( ) recursively on the rest
if (alo lt pivot) // alo in correct
half return partition(a, lo1, hi, pivot)
(this is the current lo, not the lo of the
whole array)
41How the 4-argument partition( ) works
- If alo gt pivot, then alo belongs in the upper
half of the subarray, and we swap it with ahi - We still dont know where the new alo value
should go, so we partition recursively on the
subarray that includes alo but not ahi
if (alo lt pivot) // alo in correct
half return partition(a, lo1, hi, pivot)else
// alo in wrong half swap(a, lo,
hi) return partition(a, lo, hi-1, pivot)
42How the 4-argument partition( ) works
- The base case is the one element subarray, when
lohi. 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)
434-argument partition( )
int partition(double a, int lo, int hi,
double pivot) if (hi lo) if (alo lt
pivot) return lo else return lo-1 else
if (alo lt pivot) // alo in correct
half return partition(a, lo1, hi, pivot) else
// alo in wrong half swap(a, lo,
hi) return partition(a, lo, hi-1, pivot)
44Example of partition
1
4
2
5
3
6
lo
hi
- Choose the median from among
1
4
2
5
3
6
lo
hi
lo1
(lohi)/2
1
3
2
5
4
6
pivot
45Example of partition
- Now, partition the subarray (not counting pivot)
a is now the new subarray
1
3
2
5
4
6
lo
hi
pivot
- alo gt pivot, so swap it with ahi, and
continue with the partition
5
3
2
1
4
6
lo
hi
pivot
46Example of partition
- alo is now less than pivot, so we leave it and
continue with the partition
5
3
2
1
4
6
pivot
lo
hi
- Now alo is greater than pivot, so we swap it
with ahi and continue with the partition
5
3
2
1
6
4
pivot
lo
hi
47Example of partition
- alo is again greater than pivot, so we swap it
with ahi and continue with the partition
5
3
6
1
2
4
pivot
lo
hi
- alo 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
Now were ready to recursively quicksort the left
and right subarrays
5
3
6
1
4
2
pivot
48quicksort( )
private void quicksort(double a, int lo, int
hi) int m if (hi gt lo1) // there are at
least 3 elements // so sort recursively m
partition(a, lo, hi) quicksort(a, lo,
m-1) quicksort(a, m1, hi) else // 0, 1,
or 2 elements, so sort directly if (hi lo1
Alo gt Ahi) swap(a, lo, hi)
49Performance Comparison
- Quicksort
- Best case O(nlog2n)
- Worst case O(n2) when the pivot is always the
second-largest or second-smallest element (since
medianLocation wont 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)