Uit 4, Part 3 Recursio Computer Sciece S-111 Harvard Uiversity David G. Sulliva, Ph.D. Review: Method Frames Whe you make a method call, the Java rutime sets aside a block of memory kow as the frame of that method call. mai umber othernumber The frame is used to store: the formal parameters of the method ay local variables variables declared withi the method A give frame ca oly be accessed by statemets that are part of the correspodig method call.
Frames ad the Stack The frames we've bee speakig about are stored i a regio of memory kow as the stack. For each method call, a ew frame is added to the top of the stack. public class Foo { public static it y(it i) { it j = i * 3; retur j; public static it x(it i) { it j = i - 2; retur y(i + j); public static void mai(strig[] args) { System.out.pritl(x(5)); i 8 j 24 i 5 j 3 args Whe a method completes, its stack frame is removed. y(8) x(5) Iteratio Wheever we've ecoutered a problem that requires repetitio, we've used iteratio i.e., some type of loop. Sample problem: pritig the series of itegers from 1 to 2, where 1 <= 2. example: pritseries(5, 10) should prit the followig: 5, 6, 7, 8, 9, 10 Here's a iterative solutio to this problem: public static void pritseries(it 1, it 2) { for (it i = 1; i < 2; i++) { System.out.prit(i + ", "); System.out.pritl(2);
Recursio A alterative approach to problems that require repetitio is to solve them usig a method that calls itself. Applyig this approach to the prit-series problem gives: public static void pritseries(it 1, it 2) { if (1 == 2) { System.out.pritl(2); else { System.out.prit(1 + ", "); pritseries(1 + 1, 2); A method that calls itself is a recursive method. This approach to problem-solvig is kow as recursio. Tracig a Recursive Method public static void pritseries(it 1, it 2) { if (1 == 2) { System.out.pritl(2); else { System.out.prit(1 + ", "); pritseries(1 + 1, 2); What happes whe we execute pritseries(5, 7)? pritseries(5, 7): System.out.prit(5 + ", "); pritseries(6, 7): System.out.prit(6 + ", "); pritseries(7, 7): System.out.pritl(7); retur retur retur
Recursive Problem-Solvig Whe we use recursio, we solve a problem by reducig it to a simpler problem of the same kid. We keep doig this util we reach a problem that is simple eough to be solved directly. This simplest problem is kow as the base case. public static void pritseries(it 1, it 2) { if (1 == 2) { // base case System.out.pritl(2); else { System.out.prit(1 + ", "); pritseries(1 + 1, 2); The base case stops the recursio, because it does't make aother call to the method. Recursive Problem-Solvig (cot.) If the base case has't bee reached, we execute the recursive case. public static void pritseries(it 1, it 2) { if (1 == 2) { // base case System.out.pritl(2); else { // recursive case System.out.prit(1 + ", "); pritseries(1 + 1, 2); The recursive case: reduces the overall problem to oe or more simpler problems of the same kid makes recursive calls to solve the simpler problems
Structure of a Recursive Method recursivemethod(parameters) { if (stoppig coditio) { // hadle the base case else { // recursive case: // possibly do somethig here recursivemethod(modified parameters); // possibly do somethig here There ca be multiple base cases ad recursive cases. Whe we make the recursive call, we typically use parameters that brig us closer to a base case. Tracig a Recursive Method: Secod Example public static void mystery(it i) { if (i <= 0) { // base case retur; // recursive case System.out.pritl(i); mystery(i 1); System.out.pritl(i); What happes whe we execute mystery(2)?
Pritig a File to the Cosole Here's a method that prits a file usig iteratio: public static void prit(scaer iput) { while (iput.hasnextlie()) { System.out.pritl(iput.extLie()); Here's a method that uses recursio to do the same thig: public static void pritrecursive(scaer iput) { // base case if (!iput.hasnextlie()) { retur; // recursive case System.out.pritl(iput.extLie()); pritrecursive(iput); // prit the Pritig a File i Reverse Order What if we wat to prit the lies of a file i reverse order? It's ot easy to do this usig iteratio. Why ot? It's easy to do it usig recursio! How could we modify our previous method to make it prit the lies i reverse order? public static void pritrecursive(scaer iput) { if (!iput.hasnextlie()) { // base case retur; Strig lie = iput.extlie(); System.out.pritl(lie); pritrecursive(iput); // prit the
Pritig a File i Reverse Order (cot.) A iterative approach to reversig the file would eed to: read all of the lies i the file ad store them i a temporary data structure (e.g., a array) retrieve the lies from the data structure ad prit them i reverse order The recursive method does't eed a separate data structure. the lies are stored i the stack frames for the recursive method calls! A Recursive Method That Returs a Value Simple example: summig the itegers from 1 to public static it sum(it ) { if ( <= 0) { retur 0; it = sum( 1); retur + ; Example of this approach to computig the sum: sum(6) = 6 + sum(5) = 6 + 5 + sum(4)
Tracig a Recursive Method public static it sum(it ) { if ( <= 0) { retur 0; it = sum( 1); retur + ; What happes whe we execute it x = sum(3); from iside the mai() method? Tracig a Recursive Method o the Stack public static it sum(it ) { if ( <= 0) { retur 0; it = sum( 1); retur + ; Example: sum(3) 3 2 3 1 2 3 time base case 0 retur 0 1 2 3 1 0 retur 1+0 2 3 2 1 retur 2+1 3 3 3 retur 3+3 fial result: 6
Aother Optio for Tracig a Recursive Method public static it sum(it ) { if ( <= 0) { retur 0; it = sum( 1); retur + ; Ifiite Recursio We have to esure that a recursive method will evetually reach a base case, regardless of the iitial iput. Otherwise, we ca get ifiite recursio. produces stack overflow there's o room for more frames o the stack! Example: here's a versio of our sum() method that uses a differet test for the base case: public static it sum(it ) { if ( == 0) { retur 0; it = sum( 1); retur + ; what values of would cause ifiite recursio?
Desigig a Recursive Method 1. Start by programmig the base case(s). What istace(s) of this problem ca I solve directly (without lookig at aythig smaller)? 2. Fid the recursive substructure. How could I use the solutio to ay smaller versio of the problem to solve the overall problem? 3. Do oe step! 4. Delegate the to recursio! Processig a Strig Recursively A strig is a recursive data structure. It is either: empty ("") a sigle character, followed by a strig Thus, we ca easily use recursio to process a strig. process oe or two of the characters make a recursive call to process the of the strig Example: prit a strig vertically, oe character per lie: public static void pritvertical(strig str) { if (str == ull str.equals("")) { retur; System.out.pritl(str.charAt(0)); // first char pritvertical(str.substrig(1)); // of strig
Short-Circuited Evaluatio The secod operad of both the && ad operators will ot be evaluated if the result ca be determied o the basis of the first operad aloe. expr1 expr2 if expr1 evaluates to true, expr2 is ot evaluated, because we already kow that expr1 expr2 is true example from the last slide: if (str == ull str.equals("")) { retur; // if str is ull, we wo't check for empty strig. // This prevets a ull poiter exceptio! expr1 && expr2 if expr1 evaluates to, expr2 is ot evaluated, because we already kow that expr1 && expr2 is. Coutig Occurreces of a Character i a Strig Let's desig a recursive method called umoccur(). umoccur(c, s) should retur the umber of times that the character c appears i the strig s Method defiitio:
Tracig a Recursive Method o the Stack public static it umoccur(char c, Strig s) { if (s == ull s.equals("")) { retur 0; else { it = umoccur(c, s.substrig(1)); if (s.charat(0) == c) { retur 1 + ; else { retur ; base case s "" umoccur('a', "aha") retur 0 s "aha" s "ha" s "aha" s s "a" "ha" s "aha" time s s "a" "ha" s "aha" s "a" 0 retur 1+0 s "ha" s "aha" s "ha" 1 retur 1 s "aha" s "aha" 1 retur 1+1 Commo Mistake This versio of the method does ot work: public static it umoccur(char c, Strig s) { if (s == ull s.equals("")) { retur 0; it cout = 0; if (s.charat(0) == c) { cout++; umoccur(c, s.substrig(1)); retur cout;
Aother Faulty Approach Some people make cout "global" to fix the prior versio: public static it cout = 0; public static it umoccur(char c, Strig s) { if (s == ull s.equals("")) { retur 0; if (s.charat(0) == c) { cout++; umoccur(c, s.substrig(1)); retur cout; Not recommeded, ad ot allowed o the problem sets! Problems with this approach? Testig for a Prefix Let's desig a recursive method called isprefix(). isprefix(str1, str2) should retur: true if str1 is a prefix of str2 false if str1 is ot a prefix of str2 examples: isprefix("recur", "recurse") should retur true isprefix("record", "recurse") should retur false Special case: we will cosider the empty strig ("") to be the prefix of ay strig. Thikig recursively:
Testig for a Prefix (cot.) Put the method defiitio here: Recursio vs. Iteratio Some problems are much easier to solve usig recursio. Other problems are just as easy to solve usig iteratio. Recursio is a bit more costly because of the overhead ivolved i ivokig a method. also: i some cases, there may ot be room o the stack Rule of thumb: if it's easier to formulate a solutio recursively, use recursio, uless the cost of doig so is too high otherwise, use iteratio
Extra Practice: A Palidrome Checker A palidrome is a word or phrase that reads the same forward ad backward (igorig spaces, puctuatio, ad case). examples: "radar", "mom", "live ot o evil" ispalidrome(str) should retur true if str is a palidrome, ad false otherwise. Thikig recursively: A Palidrome Checker (cot.) Put the method defiitio here: