EECE 230 Homework 8, Fall 2012

American University of Beirut · Introduction to Programming · 16 pages · Dr. Louay Bazzi

All EECE 230 files
EECE 230 Homework 8, Fall 2012, page 1

Stuck on a question? A tutor can walk you through it.

EECE 230 Homework 8, Fall 2012, page 2 EECE 230 Homework 8, Fall 2012, page 3 EECE 230 Homework 8, Fall 2012, page 4 EECE 230 Homework 8, Fall 2012, page 5 EECE 230 Homework 8, Fall 2012, page 6 EECE 230 Homework 8, Fall 2012, page 7 EECE 230 Homework 8, Fall 2012, page 8 EECE 230 Homework 8, Fall 2012, page 9 EECE 230 Homework 8, Fall 2012, page 10

Showing the first 10 of 16 pages. Download the PDF to see all of it.

More EECE 230 files

Show the text of this homework
Common Mistakes in Recursion In this document we discuss two common mistakes when writing recursive functions. We will provide two examples of incorrect binary search. These examples may look useless and contrived, since you already have a correct version of binary search and other well-known algorithms (Merge Sort, QuickSort) in the slides. But you may be required to write some variant of these algorithms, and knowing about the common mistakes may help you to avoid them when you write a variant of these well-known algorithms. Mistake#1: Unreachable base case resulting in infinite recursion: Consider the following WRONG code for recursive binary search: int recursiveBinarySearch(int * A,int start,int end, int x) { if(start <=end) { int mid = (start+end)/2; if (A[mid] < x) return recursiveBinarySearch(A,mid,end,x); else if (A[mid] > x) return recursiveBinarySearch(A,start,mid-1,x); else if (A[mid] ==x) return mid; // this is a base case } else return -1;// another base case of the recursion } This code is almost identical to the one given in the slides except that we replaced return recursiveBinarySearch(A,mid+1,end,x); by: return recursiveBinarySearch(A,mid,end,x); At first sight, the code still appears correct. It is true that if A[mid]<x , then the value x must have an index between mid and end, if it appears at all in the array. Unfortunately this code isn't correct. Try it on the input A={0,1,2,3,4,5} and x= 5. It will fail. Why ? Because the base case is unreachable: First the function is called with arguments start= 0 and end=5, then it will call itself recursively with start = 2 and end = 5, then it will call itself recursively with start = 3 and end = 5, then it will call itself with start = 4 and end =5 and then... it will call itself again with start = 4 and end =5, and the program will be stuck in an infinite recursion. Sometimes this failure will manifest itself very weirdly, for example by keeping the screen blank for a few seconds and then printing “Press any key to continue” and stopping the program. Unlike an infinite loop, infinite recursion cannot really go on very long, because each of the “workspaces” used by the recursive calls consumes memory, and very quickly the program will run out of memory. Such an error is called a stack overflow. How do you do to avoid such errors ? The general strategy is very simple: You should make sure that the recursive calls ALWAYS handle subproblems strictly smaller than the original problem, and you should make sure that the base case all problem sizes smaller than a certain threshold. In the above case the size of the right subarray was not always strictly smaller than the entire array, and that was the cause of the error. Mistake #2: Making out-of-bounds recursive calls This kind of error is common when you write recursive algorithms on arrays. Very often, recursive algorithms on arrays are divide-and-conquer algorithms which work by dividing the original arrays in ever-smaller subarrays. For example, Binary Search, Quicksort, and Merge Sort work in this fashion. Subarrays are usually specified using their first index and their last index, and these two indexes are passed as arguments. You have to make sure that you don't pass out-of- bound indexes as arguments in your recursive calls, and if you can't or don't want to avoid it, you should make sure your code can handle such invalid arguments. Example: Suppose we slightly modify the Binary Search function by adding the statement: if(A[start]==x) return start; at the very beginning of the function, and that we write the following program: #include <iostream> using namespace std; int B[] = {0,1,2,3,4,5,6,7,8,9,10}; int C[] = {11,0}; int recursiveBinarySearch(int * A,int start,int end, int x) { if(A[start]==x) return start; //Line L1 if(start <=end) { int mid = (start+end)/2; if (A[mid] < x) return recursiveBinarySearch(A,mid+1,end,x); else if (A[mid] > x) return recursiveBinarySearch(A,start,mid-1,x); else return mid; // this is a base case } else return -1;// another base case of the recursion } int main(){ int a = recursiveBinarySearch(B,0,10,11); cout << a << endl << flush; return 0; } At first sight, the Binary Search function still appears to be correct, although the first line of the function is not necessary. But if you run the above program, the output will be wrong (It will return 11, when the last valid index in array B is only 10, and the function should have returned -1). Why ? Because at some point, the start index will be outside the array. This fact is often overlooked but even if the line we added (L1) is removed, it is still true that the start variable will be greater than 10 at some time. To verify this, add a cout statement at the beginning of the recursive function to check the value of the arguments. And if B={10,11,12} and x = -1, the recursiveBinarySearch function will at some point be recursively called with a negative end index. However if Line L1 is omitted, these out- of-bounds values will not cause any error because they will not be used to index any element in the array, as the condition if(start<=end) will filter out these cases: The first time either start or end will become an invalid index, the condition (start<=end) will become false (This may not be obvious at first but will become clear with a bit of thinking.) This is so because the function was written in a way that handles this problem. OK, now assume that you have written your own recursive function, and that it does not return the correct answer, and you suspect that the code contains an out-of-bounds error. How can you find this error ? One good way would be to add a cout statement at the beginning of the recursive function, to display its arguments, especially the ones corresponding to subarray bounds, and run the program to see if some of these bounds look wrong. for example : int recursiveBinarySearch(int * A,int start,int end, int x) { cout << “ start = “ << start << “ end = “ << end << endl << flush; if(A[start]==x) return start; //Line L1 if(start <=end) { int mid = (start+end)/2; if (A[mid] < x) return recursiveBinarySearch(A,mid+1,end,x); else if (A[mid] > x) return recursiveBinarySearch(A,start,mid-1,x); else return mid; // this is a base case } else return -1;// another base case of the recursion } Please note that the cout statement ends with << flush . This is necessary when you use cout for debugging purposes. This tells the computer to display the result as soon as the cout statement is executed. If flush is omitted, the computer will sometimes store the output in a buffer, and will wait until the function stops executing to display the contents of the buffer; but if the functions fails in a runtime error, the contents of the buffer will not be displayed at all. #include <iostream> using namespace std; int recursiveCallsA=0; int recursiveCallsB=0; int recursiveCallsC=0; double iterativeA(int n); double recursiveA(int n); int iterativeB(int n); int iterativeIIB(int n); int recursiveB(int n); double iterativeIC(int n,int x,int y,int z); double iterativeIIC(int n,int x,int y,int z); double recursiveC(int n,int x,int y,int z); void main(){ int n; cout << "Please enter a strictly positive integer n " << endl; cin >> n; cout << "iterativeA(" << n << ") = " << iterativeA(n) << endl; cout << "recursiveA(" << n << ") = " << recursiveA(n) << endl; cout << "The number of calls for function recursiveA is : " << recursiveCallsA << endl; cout << "iterativeB(" << n << ") = " << iterativeB(n) << endl; cout << "iterativeIIB(" << n << ") = " << iterativeIIB(n) << endl; cout << "recursiveB(" << n << ") = " << recursiveB(n) << endl; cout << "The number of calls for function recursive B is : " << recursiveCallsB << endl; cout << "Please enter three integers x,y,z separated by spaces " << endl; int x,y,z; cin >> x >> y >> z; cout << "iterativeIC(" << n << ", " << x << ", " << y << ", " << z << ") = " << iterativeIC(n,x,y,z) << endl; cout << "iterativeIIC(" << n << ", " << x << ", " << y << ", " << z << ") = " << iterativeIIC(n,x,y,z) << endl; cout << "recursiveC(" << n << ", " << x << ", " << y << ", " << z << ") = " << recursiveC(n,x,y,z) << endl; cout << "The number of calls for function recursive C is : " << recursiveCallsC << endl; } double iterativeA(int n){ double a=0; double previous = 0; for(double i=1;i<=n;i++){ a = 2 * previous + 1; previous = a; } return a; } double recursiveA(int n){ recursiveCallsA++; if(n==1) return 1; else return 2*recursiveA(n-1)+1; } int iterativeB(int n){ //the last entry has index n,to be able to start the array at index 1 int *b = new int[n+1]; b[1] = 1; for(int i=2;i<=n;i++){ b[i] = b[i/2] + b[(i+1)/2] + i; } int returnValue = b[n]; delete [] b; return returnValue; } int recursiveB(int n){ recursiveCallsB++; if(n==1) return 1; else { //This is MUCH faster than simply returning (recursiveB(n/2) + recursiveB((n+1)/2) + n; //try both methods with n=50000000 and see the difference! if(n%2==0) return 2*recursiveB(n/2) + n; else return (recursiveB(n/2) + recursiveB((n+1)/2) + n); } } double iterativeIC(int n, int x, int y, int z){ if(n==1) return x; if(n==2) return y; if(n==3) return z; double current = z; double previous = y; double beforePrevious = x; int count = 3; while(count<n){ double temp = current + previous + beforePrevious; beforePrevious = previous; previous = current; current = temp; count++; } return current; } double iterativeIIC(int n, int x, int y, int z){ if(n==1) return x; if(n==2) return y; if(n==3) return z; double *c = new double [n+1]; c[1] = x; c[2] = y; c[3] = z; for(int i=4;i<=n;i++) c[i] = c[i-1]+c[i-2]+c[i-3]; double returnValue = c[n]; delete [] c; return returnValue; } double recursiveC(int n, int x, int y, int z){ recursiveCallsC++; if(n==1) return x; else if(n==2) return y; else if(n==3) return z; else return (recursiveC(n-1,x,y,z)+recursiveC(n-2,x,y,z)+recursiveC(n- 3,x,y,z)); } //recursiveC is highly inefficient when compared to iterativeIC and iterativeIIC //because in the iterative version, the number of steps the program makes is //proportional to n, while in the recursive version it is proportional to the //number of recursive calls, which is much larger than n: suppose you want //to compute C(50). then ,say, C(45) will be called multiple times: //1: C(50)->C(49)->C(48)->C(47)->C(46)->C(45) //2: C(50)->C(49)->C(48)->C(47)->C(45) //3: C(50)->C(49)->C(48)->C(45) //4: C(50)->C(49)->C(47)->C(46)->C(45) //5: ....And so on and so forth. This effect increases as one goes deeper in the recursion //the base cases are called millions and millions of times. //For similar reasons, iterativeIIB is much faster than recursiveB. //As to iterativeB and recursiveB, recusiveB is much faster and requires //much less memory, but it would have been slower if we had used a //naive implementation that would not distinguish between odd and even n. //the reason why recursiveB is much faster than iterativeB is that //recursiveB computes only a fraction of the array entries, and //it is much less memory consuming (since recursion depth is bounded //by the logarithm of n) // OPTIONAL // iterative B without array //iterative solution without arrays. //The basic idea of iterativeIIB is that the only values of the sequence b //that will ever need to be calculated are the values b(floor(n/2^i)) and b(floor(n/2^i)+1) //where i goes from 0 to lg n . This is a bit tricky to prove, and not required for exam. //Actually it turns out that this recurrence has a closed form solution: //a(n) = n*(floor(log_2 n) + 3) - 2^((floor (log_2 n)) + 1) //see http://www.research.att.com/~njas/sequences/A033156 int iterativeIIB(int n){ int low = 1, high = 2; int bLow = 1, bHigh = 4,newLow,newHigh,newBLow,newBHigh; int n1; while(low<n){ n1 = n; while(n1/2>low){ n1 = n1/2; } newLow = n1; newHigh = n1+1; if(n1%2==0){ newBLow = 2*bLow + newLow; newBHigh = bLow+bHigh + newHigh; } else{ newBLow = bLow + bHigh + newLow; newBHigh = 2 * bHigh +newHigh; } bLow = newBLow; bHigh = newBHigh; low = newLow; high = newHigh; } return bLow; } #include<iostream> using namespace std; double power(int x, int y); double smartPower(int x, int y); void main(){ cout << power(2,10) << endl; cout << smartPower(2,10) << endl; cout << power(10,-5) << endl ; cout << smartPower(10,-5) << endl; } //for simplicity we do not check for the case where x = 0 and y = 0; double power(int x, int y){ if(y<0) return 1/power(x,-y); else if (y==0) return 1; else return x*power(x,y-1); } double smartPower(int x, int y){ if(y<0) return 1/power(x,-y); else if(y==0) return 1; else { double val = power(x,y/2) ; if(y%2==1) return val * val * x; else return val * val; } } //the recurrence is: //u(y) = u(y/2)^2 if y is even and > 0 //u(y) = u(floor(y/2))^2 * x if y is odd and > 0 //u(y) = x if y=0 //u(y) = 1/u(-y) if y<0 #include <iostream> using namespace std; void swap(int &, int &) ; int arraySmallest(int* , int , int ); void recursiveSelectionSort( int* , int , int ); void main() { int A[100]; cout << "Enter ur integers ( -999 to end ):" << endl; int x; cin >> x; int n=0; while( x!=-999 ) { A[n]=x; n++; cin >> x; } recursiveSelectionSort(A,0,n-1); for(int i=0 ; i<n ; i++ ) cout << A[i] << " "; cout <<endl; } void recursiveSelectionSort( int* A , int start , int end ) { if( start<end ) { int minIndex= arraySmallest(A,start,end); swap( A[minIndex],A[start] ); recursiveSelectionSort(A,start+1,end ); } } void swap(int &x, int &y) { int temp = x; x = y; y = temp; } int arraySmallest(int* A , int start , int end ) { int minIndex =start; for(int i=start+1; i<=end ; i++) if (A[i]<=A[minIndex]) minIndex = i; return minIndex ; } #include <iostream> using namespace std; int sequentialModeFinder(int* A, int n); int recursiveModeFinder(int* A, int start, int end); int fastIterativeModeFinder(int* A, int start, int end); void main(){ int A[] = {1,2,4,7,11,10,8,4,-9}; int n = sizeof(A)/sizeof(int); //array size; cout << "The answer of the sequential mode finder is : " << sequentialModeFinder(A,n) << endl; cout << "The answer of the recursive mode finder is : " << recursiveModeFinder(A,0,n-1) << endl; cout << "The answer of the fast iterative mode finder is : " << recursiveModeFinder(A,0,n-1) << endl; } int sequentialModeFinder(int *A,int n){ for(int i=0;i<n-1;i++){ if(A[i]>A[i+1]) return i; } return n-1; } int recursiveModeFinder(int *A, int start, int end){ if((end==start) || (end==start+1)){ if(A[start]<A[end]) return end; else return start; } int mid = (start + end)/2; if(A[mid]>A[mid+1]) //mid is either the mode or is already past the mode return recursiveModeFinder(A,start,mid); else //We did not reach the mode yet return recursiveModeFinder(A,mid,end); } int fastIterativeModeFinder(int* A, int start, int end){ while((start!=end)&&(start+1!=end)){ int mid = (start+end)/2; if(A[mid]>A[mid+1]) end = mid; else start = mid; } if(A[start]>A[end]) return start; else return end; } #include <iostream> #include <ctime> using namespace std; int partition(int *,int,int); void printArray(const int *A,int n); void randQuickSort(int A[], int low, int high); void quickSort(int A[], int low, int high); void testQuickSort(); void testRandQuickSort(); void main() { //srand is called only *once* at the beginning of the program. //Do not call srand repeatedly, for instance within a loop, because if the time interval //between 2 consecutive calls is too small, the random number generator will be seeded with the //same value. srand((unsigned)time(NULL)); testQuickSort(); testRandQuickSort(); } void testQuickSort(){ int A[]= {14,2, 11,13,6,5,15,9,8, 12, 3, 10, 4, 1, 7}; cout<<"orginal array:"<<endl; printArray(A,15); quickSort(A,0,14); cout<<"after calling quicksort:"<<endl; printArray(A,15); } void testRandQuickSort(){ int A[]= {14,2, 11,13,6,5,15,9,8, 12, 3, 10, 4, 1, 7}; cout<<"orginal array:"<<endl; printArray(A,15); randQuickSort(A,0,14); cout<<"after calling randquicksort:"<<endl; printArray(A,15); } void quickSort(int A[], int low, int high){ if(high>low){ int q = partition(A,low,high); quickSort(A,low,q-1); quickSort(A,q+1,high); } } void randQuickSort(int A[], int low, int high){ if(high>low){ int randomIndex = low + rand()%(high-low+1); swap(A[randomIndex],A[high]); int q = partition(A,low,high); randQuickSort(A,low,q-1); randQuickSort(A,q+1,high); } } //to see why this implementation of partitioning works , observe that the following //properties hold: //1-At every step of the outer while loop i and j are between low and high included, because the initial // value of i is a lower bound on j and the initial value of j is an upper bound on i.This means that // no out of bounds error is possible. //2-At every step of the outer while loop, all elements of index strictly smaller than i, if any exists, are smaller //or equal to A[high]. //3-At every step of the outer while loop, all elements of index strictly larger than j are larger or equal to A[high], //and at least one such element exists (namely A[high]); //4-When the outer while loop exits, we have i==j. Exactly one of three cases must hold: //1-A[i]>A[high] //2-A[i]= A[high] //3-A[i]<A[high] //In the first 2 cases, we can just swap A[i] with A[high], and we're done. //In the last case, since we have i==j, and since at any time A[j+1]>=A[high], //we can swap A[j+1] with A[high] (A[j+1] is not out of bounds, since the maximum //value of j, which happens to be its initial value,is high-1) int partition(int A[],int low,int high) { int i=low; if(high-low<1) return 0; int j=high-1; while (i<j) { while (A[i]<=A[high] && i<j) i++; while (A[j]>=A[high] && i<j) j--; swap(A[i],A[j]); } if(A[i]>=A[high]) { swap(A[i],A[high]); return i; } else { swap(A[i+1],A[high]); return i+1; } } void printArray(const int *A,int n) { for (int i=0;i<n;i++) cout<<A[i]<<" "; cout<<endl; } #include <iostream> using namespace std; const int fixedStringLength = 4; void printArray(const int *A,int n); void printAll(char s[],int currentIndex,int stringLength); void printAllC(char s[], int currentIndex,int stringLength,int maxWeight); void printAllPermutations(int currentPerm[],int unavailable[],int currentIndex, int permLength); void storeAll(char s[],int currentIndex,char M[][fixedStringLength+1], int& counter); void testPrintAll(); void testPrintAllC(); void testPrintAllPermutations(); void testStoreAll(); //To design a recursive algorithm, we assume we have a function that solves a problem //of the same kind, but of smaller size, and then we try to use this function to help us //solve the larger problem. As you might have noticed from previous programming assignments, //this process bears a strong similarity to mathematical induction. We will try to illustrate //this point of view using this exercise. void main() { testPrintAll(); testStoreAll(); testPrintAllC(); testPrintAllPermutations(); } void testPrintAll(){ char s[100]; printAll(s,0,6); } void testStoreAll(){ char s[fixedStringLength+1]; char M[10000][fixedStringLength+1]; int counter = 0; storeAll(s,0,M,counter); for(int i=0;i<counter;i++) cout << M[i] << endl; } void testPrintAllC(){ char s[2000]; // the prototype of printAllC is : //void printAllC(char s[], int currentIndex,int stringLength,int curWeight,int maxWeight) printAllC(s,0,8,2); } void testPrintAllPermutations(){ int av[4]={0}; int A[4]; printAllPermutations(A,av,0,4); } //As mentioned previously, when we are trying to design a recursive algorithm, we assume that //we already have a function that works on problems of smaller size. However, on this problem, //this approach does not seem to work at first sight : even if one has a function ,say, that prints //all abc strings of length 3, that doesn't make it easier to make a function that prints //all abc strings of length 4. So we have to define our subproblems in a way that makes it easy //for us to solve the original problem. That's why, instead of defining a subproblem as printing //abc strings of smaller sizes, we define a subproblem to consist in printing all abc-strings of //full size stringLength *such that their first n characters are fixed*. (currentIndex represents the //number of fixed characters,which can also be considered as the index of the first non-fixed array entry, //because arrays are 0-based). //So, let's assume we have a function that lets us print all abc strings of length stringLength having their //first n+1 characters fixed. Now,it is easy to use this function to print all abc strings of length stringLength //having their first n characters fixed : just put 'a' at the nth position and call the function that //leaves the first n+1 characters fixed, then put 'b' at the nth position and call the function again, //and then put 'c' at the nth position and call the function again. So we now have our recursive formulation. //The base case of the function is extremely easy to understand: When all the array is fixed, we only have //to print it. //This is what we're doing in the printAll function. void printAll(char s[],int currentIndex,int stringLength){ if(currentIndex==stringLength){ s[currentIndex]='\0'; cout << s << endl; return; } s[currentIndex]='a'; printAll(s,currentIndex+1,stringLength); s[currentIndex]='b'; printAll(s,currentIndex+1,stringLength); s[currentIndex]='c'; printAll(s,currentIndex+1,stringLength); } //We removed the argument stringLength, since the length is assumed to be 4 //storeAll is a very simple modification of the previous problem: instead of printing //to the screen, it prints to an array. void storeAll(char s[],int currentIndex,char M[][fixedStringLength+1], int& counter){ if(currentIndex==fixedStringLength){ for(int i=0;i<fixedStringLength;i++) M[counter][i]=s[i]; M[counter][fixedStringLength]='\0'; counter++; return; } s[currentIndex]='a'; storeAll(s,currentIndex+1,M,counter); s[currentIndex]='b'; storeAll(s,currentIndex+1,M,counter); s[currentIndex]='c'; storeAll(s,currentIndex+1,M,counter); } //printAllC is a bit more difficult. //The function printAllC prints all binary strings of length stringLength, having their first n //characters fixed, and containing not more than maxWeight '1' characters between index n and //index stringLength included. With this general idea in mind, it is relatively easy to give a sketch of //a proof of correctness for printAllC, by induction on stringLength-n: //for a value of stringLength-n=0, all the characters are fixed , and this function is obviously //correct. Suppose now this function is correct for stringLength-n = z (n>0). Let's prove it correct //stringLength-n=z+1; the function first sets the nth element to '0' and calls itself recursively //on a larger n,and therefore a smaller value of stringLength -n. But we assumed it was correct for smaller values //of stringLength-n, and therefore it will print correctly all output strings having a '0' in the nth //place. //after having taken care of the case where the nth character is a '0' ,we now consider the case //where the nth character is a '1'. First we check if ones are at all allowed, then we set the nth //character to '1' , we freeze the n+1 first characters, and we call the function recursively, without //forgetting to decrease the number of allowable '1' characters by one. Since the function will //work correctly on this subproblem (by induction), the function will work correctly when the //nth character is set to '1'. Therefore printAllC will always work correctly. void printAllC(char s[], int n,int stringLength,int maxWeight){ if(n==stringLength){ s[n]='\0'; cout << s << endl; return; } s[n]='0'; printAllC(s,n+1,stringLength,maxWeight); if(maxWeight>0) { s[n]='1'; printAllC(s,n+1,stringLength,maxWeight-1); } } void printArray(const int *A,int n) { for (int i=0;i<n;i++) cout<<A[i]<<" "; cout<<endl; } //printAllPermutations will print all permutations of length permLength which first currentIndex //positions are fixed. Since a given number can appear only once in a given permutation, //the numbers have already been used are flagged in the array unavailable. Of course, this //information is redundant, it is only there for efficiency purposes. void printAllPermutations(int currentPerm[],int unavailable[],int currentIndex, int permLength){ if(currentIndex==permLength){ printArray(currentPerm,permLength); return; } for(int i=1;i<=permLength;i++){ if(unavailable[i-1]==0){ currentPerm[currentIndex]=i; unavailable[i-1]=1; printAllPermutations(currentPerm,unavailable,currentIndex+1, permLength); unavailable[i-1]=0; } } }