Professional Documents
Culture Documents
though C++
Abhiram G. Ranade
Ch. 16: Arrays and Recursion
Arrays and Recursion
• Recursion is very useful for designing
algorithms on sequences
– Sequences will be stored in arrays
• Topics
– Binary Search
– Merge Sort
Searching an array
• Input: A: int array of length n, x (called “key”) : int
• Output: true if x is present in A, false otherwise.
• Natural algorithm: scan through the array and return true if found.
• Time consuming:
– Entire array scanned if the element is not present,
– Half array scanned on the average if it is present.
• Can we possibly do all this with fewer operations?
Searching a sorted array
• sorted array: (non decreasing order)
A[0] ≤ A[1] ≤ … ≤ A[n-1]
• sorted array: (non increasing order)
A[0] ≥ A[1] ≥ … ≥ A[n-1]
• How do we search in a sorted array (non
increasing or non decreasing)?
– Does the sortedness help in searching?
Searching for x in
a non decreasing sorted array A[0..n-1]
• Key idea for reducing comparisons: First compare x with the
“middle” element A[n/2] of the array.
• Suppose x < A[n/2]:
– x is also smaller than A[n/2..n-1], because of sorting
– x if present will be present only in A[0..n/2-1].
– So in the rest of the algorithm we will only search first half of A.
• Suppose x >= A[n/2]:
– x if present will be present in A[n/2..n-1]
– Note: x may be present in first half too,
– In the rest of the algorithm we will only search second half.
• How to search the “halves”?
– Recurse!
Plan
• We will write a function Bsearch which will search a region
of an array instead of the entire array.
• Region: specified using 2 numbers: starting index S, length of
region L
• When L == 1, we are searching a length 1 array.
– So check if that element, A[S] == x.
• Otherwise, compare x to the “middle” element of
A[S..S+L-1]
– Middle element: A[S + L/2]
• Algorithm is called “Binary search”, because size of the
region to be searched gets roughly halved.
The code
bool Bsearch(int A[], int S, int L, int x)
// Search for x in A[S..S+L-1]
{
if(L == 1) return A[S] == x;
int H = L/2;
if(x < A[S+H]) return Bsearch(A, S, H, x);
else return Bsearch(A, S+H, L-H, x);
}
int main(){
int A[8]={-1, 2, 2, 4, 10, 12, 30, 30};
cout << Bsearch(A,0,8,11) << endl;
// searches for 11.
}
How does the algorithm execute?
• A = {-1, 2, 2, 4, 10, 12, 30, 30}
• First call: Bsearch(A, 0, 8, 11)
– comparison: 11 < A[0+8/2] = A[4] = 10
– Is false.
• Second call: Bsearch(A, 4, 4, 11)
– comparison: 11 < A[4+4/2] = A[6] = 30
– Is true.
• Third call: Bsearch(A, 4, 2, 11)
– comparison: 11 < A[4+2/2] = A[5] = 12
– Is true.
• Fourth call: Bsearch(A, 5, 1, 11)
– Base case. Return 11 == A[5]. So false.
Proof of correctness 1
Claim: Bsearch(A,S,L,x) returns true Iff x is present in A[S,S+L-1], where 0 <=
S,S+L-1 < length of A, where A is an array sorted in non decreasing order.
int main(){
const int n=8;
int y[n];
search(n, y, 0);
}
Recursion tree for n=3
Search(3,[-,-,-],0)
Search(3,[1,2,0],3) . . . ...
... ... ...
An improvement: Early check
S(i0,i1,…,ik-1) : Set of configurations of k queens such that
queen in column j is in row ij for j=0..k-1.
• If any of the first k queens capture each other, we
need not worry about the remaining n-k queens,
clearly there is no non capturing configuration in this
Subspace.
• Key idea: whenever we place the kth queen, we first
check if it captures the previous queens. Only then
do we bother to explore the set of configurations
further.
Checking if the kth queen captures any
previous queens
bool lastCaptures(int y[], int k){
// check whether queen in column k
// captures any in columns 0..k-1.
for(int j=0; j<k; j++){
if((y[j] == y[k]) ||
(abs(j-k) == abs(y[j]-y[k]))
return true;
}
return false;
}
Search function and main program
void search(int n, int y[], int k){
if(k == n){
for(int j=0; j<k; j++) cout << y[j];
cout << endl;
}
else {
for(int j=0; j<n; j++){
y[k] = j;
if(!lastCaptures(y,k)) search(n,y,k);
}
}
}
// Precisely state what this function expects and does.
// main program:
// as before.
Remarks
• Recursion is very powerful even with arrays.
• Idea of mergesort: divide input into parts, sort
each part, then combine, is called “divide-
conquer-combine”.
• “Divide-conquer-combine” is useful for other
problems besides sorting.
• “Try out all possibilities” works for many
problems, see problems at the end of the chapter.
• “Early condition checking” also works quite often.
Exercise 1
Suppose the comparison in our binary search
code used <= rather than <.
• Give an instance (set of input values) for which
the algorithm will produce a wrong answer.
• Where would the proof not work?
Exercise 2
Suppose the binary search code returns the
index of the last element it examines, rather
than a Boolean value. What value would this be,
assuming (1) the element x being searched is
not present in the array, (2) there is exactly one
occurrence of x in the array, (3) there are
multiple occurrences of x?
Exercise 3
Instead of specifying the subarray to be
searched by giving the starting index and the
length, you might give the starting and ending
indices. Write the code using this, and prove its
correctness.
Exercise 4
Suppose I represent a set of integers using an array that
holds the integers in sorted order. Given two such
arrays representing two sets, give an algorithm that
prints their union. (The common elements must be
printed just once.) Your algorithm should run in time
proportional to the size of the union.