1 / 107

Lecture #8

Lecture #8. Recursion How to design an Object Oriented program. Recursion!. Writing a computer program that can play chess or checkers !. Building a SuDoKu solver!. Even cracking codes and ciphers !.

Télécharger la présentation

Lecture #8

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Lecture #8 • Recursion • How to design an Object Oriented program

  2. Recursion! Writing a computer program that can play chess or checkers! Building a SuDoKu solver! Even cracking codes and ciphers! Recursion is one of the most difficult topics in Computer Science… But once you master it, you can solve all sorts of cool problems!

  3. Recursion Problem Solving Idea Behind SolveAProblem(problem) Just return the answer Yes Is the problem trivially solved No Break the problem into twoor more simpler sub-problems SolveAProblem(sub-problem1) SomeOtherFunction(sub-problem1) Solve each sub-problem j ... by calling some otherfunction on the sub-problem j by calling ourself! SolveAProblem(sub-problemn) SomeOtherFunction(sub-problemn) Collect all the solution(s) to the sub-problems Return the solution Use the sub-solutions to constructa solution to the complete problem

  4. “The Lazy Person’s Sort” 6 22 17 3 95 14 6 22 17 3 95 14 Let’s design a new sorting algorithm, called the “lazy person’s sort”… The input to this sort are a bunch of index cards with #s. Lazy Person’s Sort: Split the cards into two roughly-equal piles Hand one pile to nerdy student A and ask them to sort it Hand the other pile to nerdy student B and ask them to sort it Take the two sorted piles and merge them into a single sorted pile

  5. “The Lazy Person’s Sort” 99322 2774 61492 17 3 6 14 22 95 17 Pretty good! All I had to do was merge two piles of sorted cards! (My nerdy students did all the real work!) Well that worked so well, I think I’ll have them sort the other six hundred! Lazy Person’s Sort: Split the cards into two roughly-equal piles Hand one pile to nerdy student A and ask them to sort it Hand the other pile to nerdy student B and ask them to sort it Take the two sorted piles and merge them into a single sorted pile Yeah right. Hey, you’re kind of cute when you’re angry! I think I have an idea. We can be lazy too, let’s change Carey’s algorithm just a bit. That sucks. Sorting 3 cards was OK… but 300? I don’t know where to start! <blush>Thanks</blush> But what can we do?

  6. “The Lazy Person’s Sort” 17 61492 2774 99322 say “do the Lazy Person’s Sort” say “do the Lazy Person’s Sort” hot I think I see it… The algorithm isn’t complete… What happens when a person ends up with just a single notecard. The algorithm breaks down. How can you split a single card into two equal piles?!!? Very clever, students. But your approach has one flaw, can you see it? Excellent, Mr. Smallberg. And do you have a solution? Well, it worked for Carey. Why can’t we use exactly the same process he did with our piles? And the students we give each half of our piles to can do the same thing too! Lazy Person’s Sort: Split the cards into two roughly-equal piles Hand one pile to nerdy student A and ask them to sort it Hand the other pile to nerdy student B and ask them to sort it Take the two sorted piles and merge them into a single sorted pile Then we can blow this joint and go play StarCraft! You’re a genius! So now all each person has to do is split their pile in two, hand each one to someone else and then finally merge the results! Isn’t this some kind of Pyramid scheme? Will it work? Oh yeah… One more thing. Correctamundo. And no one person has to do any complex sorting! I think so. If you just rewrite it a bit…

  7. “The Lazy Person’s Sort” 17 61492 2774 99322 studly say “do the Lazy Person’s Sort” say “do the Lazy Person’s Sort” hot Correct! Amazing, huh? By having an algorithm use itself over and over, you can solve big problems! Split the cards into two roughly-equal piles Hand one pile to nerdy student A and ask them to sort it Hand the other pile to nerdy student B and ask them to sort it Take the two sorted piles and merge them into a single sorted pile Split the cards into two roughly-equal piles Hand one pile to nerdy student A and ask them to sort it Hand the other pile to nerdy student B and ask them to sort it Take the two sorted piles and merge them into a single sorted pile Lazy Person’s Sort: If you’re handed just one card, then just give it right back. Ah, I see. When a person at the bottom of the pyramid gets a single card, they can’t split it in half… Besides it’s just one card, so it’s technically already sorted... So they just hand it back to the guy above them and let them merge it. Oh yeah. And one more thing.

  8. “The Lazy Person’s Sort” OtherSort OtherSort void MergeSort(an array) { if (array’s size == 1) return; // array has just 1 item, all done! MergeSort( first half of array ); // process the 1st half of the array MergeSort( second half of array); // process the 2nd half of the array Merge(the two array halves); // merge the two sorted halves // now the complete array is sorted } If you’re willing to assume that MergeSort actually works on a full array of data… void OtherSort(an array) { // just call merge sort! MergeSort( array ); } Lazy Person’s Sort: Split the cards into two roughly-equal piles Hand one pile to person A and say “do the Lazy Person’s Sort” Hand the other pile to person B and say “do the Lazy Person’s Sort” Take the two sorted piles and merge them into a single sorted pile If you’re handed just one card, then just give it right back. Then you should be willing to have faith that it’ll work on half an array of data too! The Lazy Person’s Sort (also known as Merge Sort) is a perfect example of a recursive algorithm! When you write a recursive function… Every time our MergeSort function is called, it breaks up its input into two smaller parts and calls itself to solve each sub-part. Your job is to figure out how the function can use itself (on a subset of the problem) to get the complete problem solved. It’s hard to believe it works! Ok – but would you agree it definitely works if we change the algorithm like this? When you add the code to make a function call itself, you need to have faith that that call will work properly (on the subset of data). If we can rely upon OtherSort to somehow properly sort each sub-array, we agree that our udpatedMergeSort will work. Correct? It takes some time to learn to think in this way, but once you “get it,” you’ll be a programming Ninja! Ok, well let me show you the way OtherSort works 

  9. The Two Rules of Recursion RULE ONE: Every recursive function must have a “stopping condition!”  The Stopping Condition (aka Base Case): Your recursive function must be able to solve the simplest, most basic problem without using recursion. Remember: A recursive function calls itself.  Therefore, every recursive function must have some mechanism to allow it to stop calling itself. 

  10. The Stopping Condition void eatCandy(int layer) { if (layer == 0) { cout << “Eat center!”; return; } cout<<“Lick layer “<<layer; eatCandy(layer-1);  } Here’s a simple recursive function that shows how to eat a tootsie-roll pop. Can you identify the stopping condition in this function? What if we didn’t have this stopping condition/base case? main() { eatCandy(3); } Right! Our function would never stop running. (We’d just keep licking forever)

  11. The Two Rules of Recursion RULE TWO: Every recursive function must have a “simplifying step”. Simplifying Step: Every time a recursive function calls itself, it must pass in a smaller sub-problem that ensures the algorithm will eventually reach its stopping condition. Remember: A recursive function must eventually reach its stopping condition or it’ll run forever. 

  12. Simplifying Code void eatCandy(int layer) { if (layer == 0) { cout << “Eat center!”; return; } cout<<“Lick layer “<<layer; eatCandy(layer-1);  } Can you identify the simplifying code in our print function? What if we didn’t havesimplifying code? Our function would never get closer to our stopping condition and never stop running. Most recursive functions simplify their inputs in one of two ways: main() { eatCandy(3); } 1. Each recursive call divides its input problem in half (like MergeSort) 2. Each recursive call operates on an input that’s one smaller than the last

  13. (Rule 2.5 of Recursion) Recursive functions should never use global, static or member variables. They should only use local variables and parameters! (So be forewarned… If your recursive functions use globals/statics/members on a test/HW, you’ll get a ZERO!)

  14. Tracing Through our Function layer layer layer 0 1 2 main() { int layers = 2; eatCandy(layers); } void eatCandy(int layer) { if (layer == 0) { cout << “Eat center!”; return; } cout<<“Lick layer “<<layer; eatCandy(layer-1);  } Hmm.. So where does our function return to? void eatCandy(int layer) { if (layer == 0) { cout << “Eat center!”; return; } cout<<“Lick layer “<<layer; eatCandy(layer-1);  } Output: It’s very difficult to trace through a function that calls itself… So, let’s use a little trick and pretend like this call is actually calling a different function (one that just happens to have the same name ). Lick layer 2 Correct! It continues running just after this line! Ok, where does our function return to? Lick layer 1 Eat center! void eatCandy(int layer) { if (layer == 0) { cout << “Eat center!”; return; } cout<<“Lick layer “<<layer; eatCandy(layer-1);  } 0 Correct! It continues running just after this line! 2 1

  15. Tracing Through Recursion (on Paper) You’re taking a CS exam and see this: How do you solve it quickly? Steps: 5. What does this print? int mystery(int a) { if (a == 0) return a+1; cout << a; if (a % 2 == 0) a = mystery(a/3); else a = mystery(a-1); return a+5; } int main() { cout << mystery(3); } 1. Put a blank sheet of paper next to the func. a = 3 2. Trace the func with your finger. A. When you hit/update a var, write its value down. B. Write all output on your original sheet. C. When you call a func: a = mystery(2); * i. Write its params down ii. Write a * to mark where to continue tracing later iii. Fold the sheet in half and continue tracing Output: 3

  16. Tracing Through Recursion (on Paper) a = mystery(2); * You’re taking a CS exam and see this: How do you solve it quickly? Steps: 5. What does this print? int mystery(int a) { if (a == 0) return a+1; cout << a; if (a % 2 == 0) a = mystery(a/3); else a = mystery(a-1); return a+5; } int main() { cout << mystery(3); } 1. Put a blank sheet of paper next to the func. a = 2 a = 3 2. Trace the func with your finger. A. When you hit/update a var, write its value down. B. Write all output on your original sheet. a = mystery(0); * C. When you call a func: i. Write its params down ii. Write a * to mark where to continue tracing later iii. Fold the sheet in half and continue tracing Output: 3 2

  17. Tracing Through Recursion (on Paper) Returning a value of 1 You’re taking a CS exam and see this: How do you solve it quickly? Steps: 5. What does this print? int mystery(int a) { if (a == 0) return a+1; cout << a; if (a % 2 == 0) a = mystery(a/3); else a = mystery(a-1); return a+5; } int main() { cout << mystery(3); } 1. Put a blank sheet of paper next to the func. D. To return from a function: a = 2 a = 3 a = 0 i. Determine what value is being returned (if any) 2. Trace the func with your finger. ii. Unfold your paper once. A. When you hit/update a var, write its value down. iii. Find the * that points to the line where you were (you’ll continue from here) B. Write all output on your original sheet. a = mystery(0); * iv. Erase the * C. When you call a func: a = mystery(2); * * i. Write its params down ii. Write a * to mark where to continue tracing later iii. Fold the sheet in half and continue tracing Output: 3 2

  18. Tracing Through Recursion (on Paper) Returning a value of 6 Returning a value of 1 1 You’re taking a CS exam and see this: How do you solve it quickly? Steps: 5. What does this print? int mystery(int a) { if (a == 0) return a+1; cout << a; if (a % 2 == 0) a = mystery(a/3); else a = mystery(a-1); return a+5; } int main() { cout << mystery(3); } D. To return from a function: a = 2 a = 3 i. Determine what value is being returned (if any) ii. Unfold your paper once. iii. Find the * that points to the line where you were (you’ll continue from here) 1 a = mystery(0); * iv. Erase the * v. Write the returned value above your function a = mystery(2); * vi. Continue tracing normally. Output: 3 2

  19. Tracing Through Recursion (on Paper) D. To return from a function: i. Determine what value is being returned (if any) ii. Unfold your paper once. Returning a value of 11 Returning a value of 6 11 6 You’re taking a CS exam and see this: How do you solve it quickly? Steps: 5. What does this print? int mystery(int a) { if (a == 0) return a+1; cout << a; if (a % 2 == 0) a = mystery(a/3); else a = mystery(a-1); return a+5; } int main() { cout << mystery(3); } a = 3 iii. Find the * that points to the line where you were (you’ll continue from here) iv. Erase the * 6 v. Write the returned value above your function a = mystery(2); * vi. Continue tracing normally. Output: 3 2 11

  20. Recursion Tracing Exercise Use the paper tracing technique to determine what the following program prints: int mystery(int a) { cout << a; if (a == 0) return 1; int b = mystery(a/2); cout << b; return b + 1; } int main() { cout << mystery(3); }

  21. Writing (Your Own) Recursive Functions: 6 Steps What if we want to write our own recursive function? How do we do it? Step #1: Write the function header Let’s use these steps to write a recursive function to calculate factorials. Step #2: Define your magic function Step #3: Add your base case code Recall, the definition of fact(N) is: 1 for N = 0 N * fact (N-1) for N > 0 Step #4: Solve the problem using the magic function Step #5: Remove the magic Step #6: Validate your function

  22. Example #1: Factorial

  23. Step #1: Write the function header Figure out what argument(s) your function will take and what it needs to return (if anything). First, a factorial function takes in an integer as a parameter, e.g., factorial(6). fact(int n) int { } Second, the factorial computes (and should return) an integer result. Let’s add a return type of int. And here’s how we’d call our factorial function to solve a problem of size n… So far, so good. Let’s go on to step #2. int main() { int n = 6, result; } result = fact(n);

  24. Step #2: Define your magic function Pretend that you are given a magic function that can compute a factorial.It’s already been written for you and is guaranteed to work! For example, perhaps magicfact looks like this?!?! intmagicfact(int x) { int f = 1; while (x > 1) { f *= x; x--; } return f; } // provided for your use! intmagicfact(int x) { … } int fact(int n) It takes the same parameters as your factorial function and returns the same type of result/value. { } fact(int n) int // don’t worry about how your// function will actually work! // we’ll figure that out later!! { } There’s only one catch! You are forbidden from passing in a value of n to this magic function. So you can’t use it to compute n! But you can use it to solve smaller problems, like (n-1)! or (n/2)!, etc. int main() { int n = 6, result; } // use magicfact to solve subproblems result = magicfact( n-1 ); Show how you could use this magic function to compute (n-1)!. result = magicfact(n);

  25. Step #3: Add your base case Code Determine your base case(s) and write the code to handle them without recursion! Our goal in this step is to identify the simplest possible input(s) to our function… // provided for your use! intmagicfact(int x) { … } int fact(int n) { } fact(int n) int And then have our function process those input(s)without calling itself (i.e., just like a normal function would). // don’t worry about how your// function will actually work! // we’ll figure that out later!! { } if (n == 0) return 1; // base case Ok, so what is the simplest factorialwe might be asked to compute? // Always consider all possible // base cases and add checks // for them before proceeding! Well, the user could pass 0 into our function. 0!, by definition, is equal to 1. Let’s add a check for this and handle it without using any recursion. int main() { int n = 6, result; } // use magicfact to solve subproblems result = magicfact( n-1 ); In this example, this is the only base condition, but some problems may require 2 or 3 different checks.

  26. Step #4: Solve the problem using the magic function Now try to figure out how to use the magic function in your new function to help you solve the problem. But computing the factorial of N-1 is not so earow.This is definitely a smaller problem, perfect for our magic function to solve for us! It’s trivial to compute N, since we already know its value! So there’s no need to use our magic function to help. Unfortunately, you can’t use the magic function to do all the work for you…(it can’t solve problems of size n) // provided for your use! intmagicfact(int x) { … } int fact(int n) { } fact(int n) int // don’t worry about how your// function will actually work! // we’ll figure that out later!! { } So let’s try to break our problem into two (or more) simpler sub-problems and use our magic function to solve those. if (n == 0) return 1; // base case Hopefully, we can agree that if this magic function does what it’s supposed to… Then our new function will work correctly! int part1 = n; int result = magicfact(n); return result; Well, by definition, N! = N * (N-1)! So it’s already split into two parts for us, & each part is simpler than the original problem. int part2 = return part1 * part2; int main() { int n = 6, result; } Let’s figure out a way to solve each of these sub-problems. // use magicfact to solve subproblems result = magicfact( n-1 ); Cool! Now we can combine the results of our sub-problems to get the overall result! magicfact( n-1 );

  27. Step #5: Remove the magic intmagicfact(int x){ return fact(x); } OK, so let’s see what this magic function really looks like! Wait a second! Our magicfact function basically just calls fact! That means that fact is really just calling itself! But if our function calls itself, what stops it from running on forever? Right! Our base case code ensures that our function eventually stops calling itself! // provided for your use! intmagicfact(int x) { … } int fact(int n) { } fact(int n) int // don’t worry about how your// function will actually work! // we’ll figure that out later!! { } if (n == 0) return 1; // base case The magicfactfunction hid this from us, but that’s what’s really happening! int part1 = n; fact( n-1 ); magic int part2 = Woohoo! We’ve just created our first recursive function from scratch! OK, well in that case, let’s replace our call(s) to the magic function with calls directly to our own function. return part1 * part2; int main() { int n = 6, result; } Will that work? Yup! // use magicfact to solve subproblems result = magicfact( n-1 );

  28. Step #6: Validating our Function You SHOULD do this step EVERY time your write a recursive function! Start by testing your function with the simplest possible input. Next test your function with incrementally more complex inputs. (You can usually stop once you’ve validated at least one recursive call) 0 int fact(int n) { if (n == 0) return 1; return n * fact(n-1); } Excellent! We’ve tested all of the base case(s) as well as validated a single level of recursion… We can be pretty certain our function works now… 0 == 0 1 Good. This result is correct.0!is equal to1. 0 1 Good. This result is correct too.1!is equal to 1 * 0!, aka 1 * 1, aka 1. int fact(int n) { if (n == 0) return 1; return n * fact(n-1); } int main() { } 1 == 0 0 == 0 cout << fact( 0 ); cout << fact( 1 ); 1 0

  29. Factorial Trace-through n 0 n 1 n3 result n2 int fact(int n) { if (n == 0) return (1); return(n * fact(n-1)); } 0 == 0 int fact(int n) { if (n == 0) return (1); return(n * fact(n-1)); } 1 1 == 0 0 1 1 int fact(int n) { if (n == 0) return (1); return(n * fact(n-1)); } 2 == 0 1 2 2 6 3 int fact(int n) { if (n == 0) return (1); return(n * fact(n-1)); } int main() { int result; result = fact(3); cout << result; } 3 == 0 2 6 6

  30. Example #2: Recursion on an Array arr 10 20 70 14 39 n-1 0 For our next example, let’s learn how to use recursion to get the sum of all the items in an array.

  31. Step #1: Write the function header You could also have written: int*arr It’s the same thing! Figure out what argument(s) your function will take and what it needs to return (if anything). int sumArr(int arr[], int n) To sum up all of the items in an array, we need a pointer to the array and its size. { } Our function will return the total sum of items in the array, so we can make the return type an int. And here’s how we’d call our array-summer function to solve a problem of size n… int main() { constint n = 5; intarr[n] = { 10, 100, 42, 72, 16}, s; } s = sumArr( arr , n); // whole array So far, so good. Let’s go on to step #2.

  32. Step #2: Define your magic function Pretend that you are given a magic function that sums up the values in an array and returns the result… // provided for your use! intmagicsumArr(intarr[], int x) { … } Since we used n/2 to specify the # of items in the first half of the array. There are n – n/2 items in the second half of the array. e.g., if n=5, then n/2 == 2 and n-n/2 == 3 int sumArr(int arr[], int n) There’s only one catch! You are forbidden from passing in an array with n elements to this function. { } If you recall from the pointer lecture, this is called “pointer arithmetic.” This means “give me a pointer to the item in the array that is 1 element from the array’s start,” e.g., &arr[1] So you can’t use it to sum up an entire array (one with all n items)… int main() { constint n = 5; intarr[n] = { 10, 100, 42, 72, 16}, s; } But you can use it to sum up smaller arrays (e.g., with n-1 elements)! Finally show how to use the magic function to sum the lasthalf of the array. Show how to use the magic function to sum the firstn-1items of the array. Now show how to use the magic function to sum the lastn-1 items of the array. Now show how to use the magic function to sum the firsthalf of the array. s = magicsumArr( arr, n-1 ); // first n-1 s = magicsumArr(arr+1, n-1 ); // last n-1 s = magicsumArr(arr, n); arr+1, n-1 s = magicsumArr( arr, n/2); // sums 1st half arr+n/2, n–n/2 s = magicsumArr( arr+n/2, n–n/2); // 2nd

  33. Step #3: Add your base case Code Determine your base case(s) and write the code to handle them without recursion! // provided for your use! intmagicsumArr(intarr[], int x) { … } int sumArr(int arr[], int n) Ok, so what is the smallest array that might be passed into our function? { } if (n == 0) return 0; if (n == 1) return arr[0]; Well, someone could pass in a totally empty array of size n = 0. What should we do in that case? Well, what’s the sum of an empty array? Obviously it’s zero. Let’s add the code to deal with this case. int main() { constint n = 5; intarr[n] = { 10, 100, 42, 72, 16}, s; } Do we have any other base cases? For example, what if the user passes in an array with just one element? s = magicsumArr( arr, n-1 ); // first n-1 s = magicsumArr(arr+1, n-1 ); // last n-1 Let’s see what that would look like… s = magicsumArr( arr, n/2); // sums 1st half s = magicsumArr( arr+n/2, n–n/2); // 2nd Good. Let’s keep both of those.

  34. Step #4: Solve the problem using the magic function Now try to figure out how to use the magic function in your new function to help you solve the problem. // provided for your use! intmagicsumArr(intarr[], int x) { … } Unfortunately, you can’t use the magic function to do all the work for you…(it can’t solve problems of size n) int sumArr(int arr[], int n) { } if (n == 0) return 0; if (n == 1) return arr[0]; int s = magicsumArr(arr, n); return s; So let’s try to break our problem into two (or more) simpler sub-problems and use our magic function to solve those. int main() { constint n = 5; intarr[n] = { 10, 100, 42, 72, 16}, s; } s = magicsumArr( arr, n-1 ); // first n-1 s = magicsumArr(arr+1, n-1 ); // last n-1 s = magicsumArr( arr, n/2); // sums 1st half s = magicsumArr( arr+n/2, n–n/2); // 2nd

  35. Step #4: Solve the problem using the magic function Now try to figure out how to use the magic function in your new function to help you solve the problem. // provided for your use! intmagicsumArr(intarr[], int x) { … } Unfortunately, you can’t use the magic function to do all the work for you…(it can’t solve problems of size n) int sumArr(int arr[], int n) Hopefully, we can agree that if the magic function does what it’s supposed to… Then our new function will work correctly! { } if (n == 0) return 0; if (n == 1) return arr[0]; So let’s try to break our problem into two (or more) simpler sub-problems and use our magic function to solve those. int front = int total = front + arr[n-1]; return total; Strategy #1: Front to back Your function uses the magic function to process the firstn-1 elements of the array, ignoring the last element. Once it gets the result from the magic function, it combines it with the last element in the array. It then returns the full result. int main() { constint n = 5; intarr[n] = { 10, 100, 42, 72, 16}, s; } s = magicsumArr( arr, n-1 ); // first n-1 magicsumArr( arr,n-1 ); s = magicsumArr(arr+1, n-1 ); // last n-1 s = magicsumArr( arr, n/2); // sums 1st half s = magicsumArr( arr+n/2, n–n/2); // 2nd

  36. Step #4: Solve the problem using the magic function Now try to figure out how to use the magic function in your new function to help you solve the problem. // provided for your use! intmagicsumArr(intarr[], int x) { … } Unfortunately, you can’t use the magic function to do all the work for you…(it can’t solve problems of size n) int sumArr(int arr[], int n) Hopefully, we can agree that if the magic function does what it’s supposed to… Then our new function will work correctly! { } if (n == 0) return 0; if (n == 1) return arr[0]; int rear= So let’s try to break our problem into two (or more) simpler sub-problems and use our magic function to solve those. int total = arr[0] + rear; return total; Strategy #2: Back to front Your function uses the magic function to process the lastn-1 elements of the array, ignoring the first element. Once it gets the result from the magic function, it combines it with the first element in the array. It then returns the full result. int main() { constint n = 5; intarr[n] = { 10, 100, 42, 72, 16}, s; } s = magicsumArr( arr, n-1 ); // first n-1 magicsumArr( arr+1,n-1 ); s = magicsumArr(arr+1, n-1 ); // last n-1 s = magicsumArr( arr, n/2); // sums 1st half s = magicsumArr( arr+n/2, n–n/2); // 2nd

  37. Step #4: Solve the problem using the magic function Now try to figure out how to use the magic function in your new function to help you solve the problem. // provided for your use! intmagicsumArr(intarr[], int x) { … } Unfortunately, you can’t use the magic function to do all the work for you…(it can’t solve problems of size n) int sumArr(int arr[], int n) Hopefully, we can agree that if the magic function does what it’s supposed to… Then our new function will work correctly! { } if (n == 0) return 0; if (n == 1) return arr[0]; int first = So let’s try to break our problem into two (or more) simpler sub-problems and use our magic function to solve those. int last = return first + last; Strategy #3: Divide and conquer Your function uses the magic function to process the firsthalf of the array. Your function uses the magic function to process the lasthalf of the array. Once it gets both results, it combines them and returns the full result. int main() { constint n = 5; intarr[n] = { 10, 100, 42, 72, 16}, s; } s = magicsumArr( arr, n-1 ); // first n-1 s = magicsumArr(arr+1, n-1 ); // last n-1 magicsumArr( arr,n/2 ); s = magicsumArr( arr, n/2); // sums 1st half magicsumArr( arr+n/2, n–n/2 ); s = magicsumArr( arr+n/2, n–n/2); // 2nd

  38. Step #5: Remove the magic intmagicsumArr(intarr[], int x){ return sumArr(arr,x); } But if our function calls itself, what stops it from running on forever? Right! Our base case code ensures that our function eventually stops calling itself! // provided for your use! intmagicsumArr(intarr[], int x) { … } OK, so let’s see what this magic function really looks like! int sumArr(int arr[], int n) Wait a second! Our magicsumArrfunction just calls sumArr! This means that sumArris really just calling itself! { } if (n == 0) return 0; if (n == 1) return arr[0]; magic sumArr( arr,n/2); int first = intscnd = magic sumArr( arr+n/2, n–n/2 ); returnfirst + scnd; The magic function hid this from us, but that’s what’s really happening! int main() { constint n = 5; intarr[n] = { 10, 100, 42, 72, 16}, s; } OK, well in that case, let’s replace our calls to the magic function with calls directly to our own function. s = magicsumArr( arr, n-1 ); // first n-1 Will that work? Yup! s = magicsumArr(arr+1, n-1 ); // last n-1 s = magicsumArr( arr, n/2); // sums 1st half Woohoo! We’ve just created our second recursive function! s = magicsumArr( arr+n/2, n–n/2); // 2nd

  39. Step #6: Validating our Function int int sumArr(int arr[], int n) sumArr(int arr[], int n) { } { } if (n == 0) return 0; if (n == 1) return arr[0]; if (n == 0) return 0; if (n == 1) return arr[0]; You SHOULD do this step EVERY time your write a recursive function! Start by testing your function with the simplest possible input. 20 10 Next test your function with incrementally more complex inputs. (You can usually stop once you’ve validated at least one recursive call) Our simplest input is an array with no elements. So let’s pass in our array but specify a size of 0 (it’s as if the array has no items.) Excellent! We’ve tested all of the base case(s) as well as validated a single level of recursion… We can be pretty certain our function works now… This is the correct result! The sum of an empty array is zero! return first +secnd; //combine & return returnfirst +secnd; //combine & return Alright, next let’s test our function on an array with exactly one element… Again, since n=0, it’s as if the array has no items. int main() { intarr[1] = { 10, 20 }; } 10 10 20 20 This is the correct result! The sum of an array with one element is just arr[0]. 10 1 0 20 1 cout << sumArr( arr, 0 ); 2 30 int first = sumArr( arr,n/2); int first = sumArr( arr,n/2); cout << sumArr( arr, 2); intsecnd = sumArr(arr+n/2, n-n/2); intsecnd = sumArr(arr+n/2, n-n/2);

  40. Array-summer Trace-through nums 10 10 20 20 42 42 20 42 intsumArr(intarr[], int n) { if (n == 0) return 0; if (n == 1) return arr[0]; int first = sumArr( arr, n/2 ); intsecnd = sumArr( arr+n/2, n-n/2); return first + secnd;} intsumArr(intarr[], int n) { if (n == 0) return 0; if (n == 1) return arr[0]; int first = sumArr( arr, n/2 ); intsecnd = sumArr( arr+n/2, n-n/2); return first + secnd;} intsumArr(intarr[], int n) { if (n == 0) return 0; if (n == 1) return arr[0]; int first = sumArr( arr, n/2 ); intsecnd = sumArr( arr+n/2, n-n/2); return first + secnd;} 42 20 10 1 20 1 42 62 int main() { constint n = 3; intnums[n] = { 10, 20, 42 }; cout << sumArr( nums , n ); } 1 10 2 3 72

  41. Your Turn: Recursion Challenge Write a recursive function called printArr that prints out an array from top to bottom. Step #1: Write the function header Step #2: Define your magic function Step #3: Add your base case code Step #4: Solve the problem using the magic function Step #5: Remove the magic Step #6: Validate your function

  42. Recursion Challenge Step #1: Write the function header Write a recursive function called printArr that prints out an array from top to bottom. Step #2: Define your magic function Step #3: Add your base case code Step #4: Solve the problem using your magic function Step #5: Remove the magic Step #6: Validate your function void printArr(intarr[], int size){} // just assume it works (like magic)! void magicprintArr(intarr[], int s) {…} int main(){constint size = 5;intarr[size] = {7, 9, 6, 2, 4}; } First, we’ll print out the zeroth element in the array ourself. if (size == 0) return; Then we’ll ask our magic function to print the remaining n-1 elements of the array for us. cout << arr[0]; // show how to use your magic func// to print the last n-1 elems magicprintArr(arr+1,size-1); printArr(arr,0); printArr(arr,1); printArr(arr+1,size-1); magicprintArr(arr+1,size-1);

  43. Recursion Challenge #2 Update your recursive function so it prints out the items from bottom-to-top. HINT: You can swap just two lines in your previous function! void printArr(intarr[], int size){} First we’ll use recursion to print out the last n-1 elements of the array in reverse order. if (size == 0) return; cout << arr[0]; Then, we’ll print out the first element in the array afterward. printArr(arr+1,size-1);

  44. Working Through Recursion Now it looks like arr has just one element and starts at 2040! names arr [0] [1] [2] void reversePrint(string arr[ ], int size) { } void reversePrint(string arr[ ], int size) { } void reversePrint(string arr[ ], int size) { } if (size == 0) // an empty array return; if (size == 0) // an empty array return; if (size == 0) // an empty array return; else { reversePrint(arr + 1, size – 1 ); cout << arr[0] << “\n”; } else { reversePrint(arr + 1, size – 1 ); cout << arr[0] << “\n”; } else { reversePrint(arr + 1, size – 1 ); cout << arr[0] << “\n”; } arr arr arr arr [0] size size size arr [0] [1] 2040 Cool! It’s if our array starts at location 2020 and only has 2 elements! 2000 2020 2040 Leslie Phyllis Nan 2020 2020 + 1 1 main() { string names[3]; ... reversePrint(names,3); } arr [0] is “ ” 2000 + 1 2 2000 3

  45. Working Through Recursion 2040 1 names arr [0] [1] [2] 2020 void reversePrint(string arr[ ], int size) { } void reversePrint(string arr[ ], int size) { } void reversePrint(string arr[ ], int size) { } void reversePrint(string arr[ ], int size) { } 2 if (size == 0) // an empty array return; if (size == 0) // an empty array return; if (size == 0) // an empty array return; if (size == 0) // an empty array return; else { reversePrint(arr + 1, size – 1 ); cout << arr[0] << “\n”; } else { reversePrint(arr + 1, size – 1 ); cout << arr[0] << “\n”; } else { reversePrint(arr + 1, size – 1 ); cout << arr[0] << “\n”; } else { reversePrint(arr + 1, size – 1 ); cout << arr[0] << “\n”; } arr arr arr arr arr arr 2000 [0] [0] size size size size 3 arr [0] [1] 2060 2000 2020 2040 Leslie Phyllis Nan We’ve hit our base case! We’ve got an empty array (of size 0) so we just return! 2040 + 1 0 main() { string names[3]; ... reversePrint(names,3); }

  46. Working Through Recursion names arr [0] [1] [2] void reversePrint(string arr[ ], int size) { } void reversePrint(string arr[ ], int size) { } void reversePrint(string arr[ ], int size) { } if (size == 0) // an empty array return; if (size == 0) // an empty array return; if (size == 0) // an empty array return; else { reversePrint(arr + 1, size – 1 ); cout << arr[0] << “\n”; } else { reversePrint(arr + 1, size – 1 ); cout << arr[0] << “\n”; } else { reversePrint(arr + 1, size – 1 ); cout << arr[0] << “\n”; } arr arr arr arr [0] size size size arr [0] [1] 2040 arr [0] is “ ” 1 2000 2020 2040 Nan Leslie Phyllis Nan 2020 arr [0] is “ ” 2 Phyllis main() { string names[3]; ... reversePrint(names,3); } 2000 arr [0] is “ ” arr [0] is “ ” 3 Leslie 2000 3

  47. Example #3: Recursion on a Linked List 1200 head … val val val 42 18 next next next 1400 1300 39 nullptr When we process a linked list using recursion, it’s very much like processing an array using strategy #2! struct Node { intval; Node *next; }; • There are two differences: • Instead of passing in a pointer to an array element, you pass in a pointer to a node • You don’t need to pass in a size value for your list (this is determined via the next pointers) Let’s see an example. We’ll write a function that finds the biggest number in a NON-EMPTY linked list.

  48. Step #1: Write the function header Figure out what argument(s) your function will take and what it needs to return (if anything). struct Node { intval; Node *next; }; To find the biggest item in a linked list, what kind of parameter should we pass to our function? int biggest( ) Node *cur Right! All we need to pass in is a pointer to a node of the linked list. { } Our function will return the biggest value in the list, so we can make the return type an int. So far, so good. Let’s go on to step #2.

  49. Step #2: Define your magic function Pretend that you are given a magic function that finds the biggest value in a linked list and returns it… struct Node { intval; Node *next; }; Now, how can we somehow isolate just n-1 nodes from our linked list so we can pass them into our magic function? There’s only one catch! You are forbidden from passing in a full linked list with all n elements to this function. Well, if cur points to the top node of an n-element linked list… // provided for your use! intmagicbiggest(Node *n) { … } So you can’t use it to find the biggest item in the entire list (one with all n items)… Then cur->next points to a linked list withn-1 elements… int biggest( ) Node *cur So if we trust our magic function, this will give us the biggest item in the last n-1 nodes of our linked list. n elements n-1 elements { } But you can use it to find the biggest item in a partial list (e.g., with n-1 elements)! int main() {Node *cur = createLinkedList(); } Let’s see how to do this. int biggest = magicbiggest( ); cur->next int biggest = magicbiggest(cur);

  50. Step #3: Add your base case Code Determine your base case(s) and write the code to handle them without recursion! struct Node { intval; Node *next; }; For this problem, we’re assuming that the user must pass in a linked list with at least one element. // provided for your use! intmagicbiggest(Node *n) { … } So, what’s the simplest case that our function must handle? int biggest( ) Node *cur Well, if a linked list has only one node… if (cur->next == nullptr) // the only node return cur->val; // so return its value { } Nope… Since we don’t have to deal with an empty list, this is the simplest case… int main() {Node *cur = createLinkedList(); } Then by definition that node must hold the biggest (only!) value in the list, right? int biggest = magicbiggest( ); cur->next Are there any other base cases?

More Related