Download presentation

Presentation is loading. Please wait.

Published byDayna Bates Modified over 2 years ago

1
1/28 Recursion

2
2/28 Introduction to Recursion Recursive procedures are functions that invoke themselves either directly (call themselves from within themselves) or indirectly (calls another method that calls original method.) Recursion:. An alternative to iteration. Recursion can be very elegant at times,. Not inexpensive to implement... Classic examples of recursion. Recursive calculation of a string length, factorials, divide and conquer, towers of Hanoi, binary searches, and more Recursive functions are used in many applied areas.. In artificial intelligence.. In searching data structures that are themselves "recursive" in nature, such as trees. Concepts and implementation of recursive functions is an important topic.

3
3/28 int public sum (int n) { if (n <= 1) return n; else return (n + sum(n-1)); }// end sum() Looks pretty simple… Notice the recursion in the Else branch Note the ‘return’ calls itself! (calls the method it resides in!) Note the ‘Base Case’ Note the ‘Recursive Case’ Note the return instruction is not ‘ satisfied ’ until it can totally execute, that is, the machine can add n + the sum (n-1)! Let’s look how this is executed! Consider: Want to sum the numbers from 1 to 5 Base case Recursive Case

4
4/28 int public sum (int n) { if (n <= 1) return n; else return (n + sum(n-1)); }// end sum() START WITH N = 5: Look at return statement. We get a VALUE (5) plus a (recursive) ‘CALL:’ Thus the return is not “satisfied” because the call is not complete. equivalently, the return statement doesn’t have all the values. Results of the ‘return’ are suspended until we have a value for sum(n-1). But the call it ‘itself’ is initiated to get a value for sum(n-1). Thus: SUM(5): 5 + SUM(4) - A FUNCTION CALL (SUM(4)) WHOSE VALUE WE AWAIT So what does the ‘next’ call look like? SUM(4): 4 +SUM(3) -A FUNCTION CALL WHOSE VALUE WE AWAIT SUM(3): 3 + SUM(2) -A FUNCTION CALL WHOSE VALUE WE AWAIT SUM(2): 2 + SUM(1) - A FUNCTION CALL WHOSE VALUE WE AWAIT SUM(1): 1 OK. Now what? We finally have a call whose value is satisfied. So??

5
5/28 IT IS ONLY WHEN WE GET THE VALUE FOR SUM(1) = 1 (reached the base case, where we have a discrete value is assigned and execution of the instruction is complete) THAT WE CAN GO "BACK UP" (one call at a time) THROUGH THE SUSPENDED FUNCTION CALLS AND CALCULATE THEIR VALUES AND THUS "COMPLETE THAT ORIGINAL CALL"... We need to ‘satisfy’ a particular function call before we can proceed up the calling sequence chain. Note this notion of ‘completing’ the call; that is, satisfying the call….

6
6/28 WHEN WE GET SUM(1)=1, THEN WE REFER TO 2 + SUM(1) equals 2 + 1 = 3. (complete) WE NOW HAVE A VALUE FOR SUM(2). The call: SUM(2) is satisfied!! NEXT, GOING UP THE SUSPENDED CALLS.... SUM (3) = 3+SUM(2), which is 3 + 3 OR 6; (now complete) We now have a value for SUM(3) The call: SUM(3) is ‘satisfied.’ Proceed upward. NEXT, SUM (4) = 4+SUM(3), WHICH IS 4 + 6 OR 10; (now complete) FINALLY, SUM (5) = 5+SUM(4), WHICH IS 5 + 10 OR 15 (now complete) ALL THESE PREVIOUS FUNCTION CALLS ARE "PENDING" OR "INCOMPLETE" PENDING A RETURNED VALUE FROM THE CALLED FUNCTION, which was part of the return statement.

7
7/28. RECURSION => has a significant overhead…. "DEEPER" WE GO => Need ADDITIONAL COPIES OF THE DATA.. NOT ALWAYS SMALL NUMBER OF DATA ITEMS.. These ‘copies’ are stored in "STACK FRAMES“ which contain current values and outstanding function calls, and more (to be discussed). Each "CALL" results in a STACK FRAME. Each requires space, allocation of space, and processing time. NOTE:.. Recursive Functions – Not efficient from a System Resource perspective... Can pay dividends in:... Ease of writing and maintaining as compared to the writing of iterative procedures. (You will see in trees.).. Termed ‘elegant’ by some The Expense of Recursive Functions

8
8/28 Procedure : 1.Find a "Base Case " (That is what we shoot for.) 2. Develop the Recursive Case: Base Case was sum(1). We know the answer to this one! we will always have a simple *normally’ assignment statement here, such as assigning a value of null or 0 or 1. You will see. Recursive Case was sum (n-1) which will (must) eventually lead to sum(1) Your general recursive case must progress to the base case. An absolute MUST to conclude the method calls. Notion of the Base Case and Recursive Case

9
9/28 Consider: FACTORIAL FUNCTION Where n! = n* (n-1) * (n-2) *... 1! * 0! Where 1! = 1 & 0! = 1 by definition. Thus 5! = 5 * 4 * 3 * 2 * 1 = 120

10
10/28 RECURSIVE FUNCTION IS WRITTEN AS: int public factorial (int n) { if (n==1) return (1) /* THIS IS THE BASE CASE */ else return (n * factorial (n-1) ); /* RECURSIVE CASE */ }end factorial () RECALL: When a function calls itself, this implies development of a new set of local variables.. Same “variable names”but very different variables (different memory addresses) and different values! There may also be some other temporary variables during the life of a particular ‘call.’ OK, so how does this work? Classic: Factorial

11
11/28 main calls argument n = 7. factorial(7) value of n at this node: returned value n=7 return (7*factorial(6)) return(7*720) = 5040 = answer!! recursive call n=6 return(6*factorial(5)) return(6*120) = 720 n=5 return(5*factorial(4)) return(5*24) = 120 n=4 return (4*factorial(3)) return(4*6) = 24 n=3 return (3*factorial(2)) return (3 *2) = 6 n=2 return (2*factorial(1)) return( 2 * factorial(1)) = 2 * 1 = 2 Thus factorial (2) = 2. return 2. n=1 1 is substituted for the call return (1) base case reached. Classic: Factorial int public factorial (int n) { if (n==1) return (1) /* base case */ else return (n * factorial (n-1) ); /* recursive case }// end factorial()

12
12/28 "Base case" a very important notion! A base case is one that is simple; one that we are working ‘down’ toward working "down" toward, since, as it turns out, we normally go "down" or “decrease” some value or some quantity (such as length of a string) toward the base case. (e.g. We know the length of an empty string is 0; we know that 0! And 1! By definition = 1…) Determining the base case ensures the recursive function will terminate someday!! Base Case

13
13/28 Writing Recursive Programs. Have spoken about the "base case" and "recursive case." Difficulty in writing recursive routines is identifying.. base case, and (Some books calls this: "trivial case“).. Recursive case. ( Some books calls this: "complex case") Base case A case that is not recursive and directly obtainable. Will have a value of 1 or 0 or null or something having some kind of discrete value… Recursive case A case that is ultimately defined in terms of base case.

14
14/28 Complex case. Must be defined in terms of a "simpler" case. Plus the base case must be:. a directly solvable, non-recursive trivial case We develop solutions using the assumption that the simpler case has already been solved. Stated equivalently:

15
15/28 Example: Write a recursive routine that computes a*b Now, what do we know? When b = 1, case is trivial, because a*b = a. TRIVIAL CASE So, in general, then, a*b may be defined in terms of a*(b-1) with definition: a*b = a*(b -1) + a right?? Certainly 5*4 = 5*3 + 5 = 20. Here, recursion is based on the second parameter, b, alone. Can you write such a recursive routine from this knowledge? YES!

16
16/28 me:) So, how about: int public mult (int a, int b) { Easy to see:if (b == 1) return a; else We can say: return (a + mult(a, b-1)); } // end mult() Agree with this?

17
17/28 me:) int public mult (int a, int b) { if (b == 1) return a; else return (a + mult(a, b-1)); } Does this work? Let a * b 4 * 3 (that is, a is 4, b is 3). So, (brute force approach) main:call mult(4,3)....(see above) 1. Executing the code: if (b == 1) ? No it does not; b = 3, so we cannot ‘return a’ We must: return (4 + mult(4,2)); Thus the return statement is suspended… 2. Let’s execute the method again… if (b == 1) ? no. b = 2. and so: hold on to 4 and call method again as in: else return (4 + mult (4,1)); once again, the return is suspended 3. Let’s execute the method again… if (b == 1) Alas! yes. return a => return 4. (this call – mult (4,1) - is satisfied....) Next slide…

18
18/28 me:) int public mult (int a, int b) { if (b == 1) return a; else return (a + mult(a, b-1)); } So, (See 2) we have return 4 + mult(4,1) and mult(4,1) yields a 4 Thus, 4 + mult(4,1) = 4 + 4 = 8 and THIS call is "satisfied" 8 is returned for mult(4,2) And See 1. we have return 4 + mult (4,2) and mult (4,2) call returned an 8. Thus, 4 + mult(4,2) = 4 + 8 = 12. and THIS call is "satisfied" 12 is returned to main - the call mult (4,3) = 12

19
19/28 SIMULATING RECURSION Some languages do not support recursion. It is often very common to be able to come up with a recursive solutions to a problem. Since some compilers do not support recursion, we need to be able to come up with non-recursive solutions. (Iterative Routines) Recursive solutions are often more expensive than non-recursive solutions in Time and space. Authors assert: a small price to pay for logical simplicity and self- documentation of recursive solutions. Heavy use of recursive solutions for production systems may dictate non-recursive solutions to problems. Let's look at iterative solutions first.

20
20/28 CONSIDER: CALL: rout(x); FUNCTION: rout (int a) //here, nothing is specified as return type. x is the ‘argument’ (sometimes called ‘actual parameter’) a is the ‘formal parameter.’ When we call a function: 1. Pass arguments. Values are “copied” to automatic local variable (for Call by Value) 2. Allocate and initialize local variables. Local declarations are declared at start. Any temporaries declared during execution 3. Transferring control to/from the function. Need a return address.. Function returns via a branch back.. Function stores this address in its own area. When we return from a function: 1. Retrieve return address. 2. Free up data areas. Local variables, argument information, temporaries and return address storage space. 3. Execute the branch and transfer values ‘back’ Control is returned to the statement calling the function. Value is typically stored in a register where calling program can retrieve it.

21
21/28 IMPLEMENTING RECURSIVE FUNCTIONS. What more does implementation of recursive solutions require? In recursion: an entirely new data area for each particular call is allocated. Data area contains all parameters, local variables, temporary data, and a return address, as stated. The data area is not associated with the function, but with a specific call to the function (and with EACH call). Each call causes a new data area to be allocated. Each reference in the executable code is to the data of the most recent call. Return? Current data area is freed up, and the data area allocated immediately prior to current area is now ‘current.’ All this, of course, implies a stack, just as in iterative case, where there is a stack of return addresses.

22
22/28 Previews of coming distractions: –Consider an iterative approach to processing a binary tree (push and pop) Define binary tree and a LNR traversal a ‘tree’ is a recursive data structure…

23
23/28 Stacks are Used. (Stack Frames) In practice, we use a single large stack, with each element of the large stack being an entire data area containing data for a specific call. In recursive returns we may do some / all of the following: we pop the stack to get to: Returned values Returned addresses Data areas Branch to statement invoking function taken. Calling function regains control and continues for a particular ‘call’ or message invocation

24
24/28 EFFICIENCY OF RECURSION. Generally speaking, non-recursive versions will execute more efficiently (time / space). Why? Overhead involved in entering and exiting blocks is avoided in non-recursive solutions. Often, in iterative solutions, we have a number of local variables and temporaries that do not have to be saved and restored via a stack. But, non-recursive solutions may cause needless stacking activity (you will have to do it yourself). In recursive solutions, compiler is usually unable to identify some variables and these are therefore stacked and unstacked to ensure there are no problems (beyond us here…)

25
25/28 In Practice: Conflicts There are conflicts between –machine efficiency and –programmer efficiency It is more efficient for the machine to lose some efficiency than a high-cost programmer. It may not be worth the effort to construct a non-recursive solution for a problem solved naturally recursively.

26
26/28 Recursive routines can be as fast or faster if you can eliminate some of the stacks and when recursive versions do not contain any extra parameters and local variables that may need saving… Note:. factorial does not need a stack.. Fibonacci numbers contain unnecessary second recursive calls (and also does not need a stack). Thus, in these, recursion should be avoided. Also,Push(), pop(), IsEmpty() and tests for overflow, underflow are expensive in writing iterative routines. (You will see when we get to trees) This expense can outweigh expensive and overhead of recursion. Thus to maximize actual run-time efficiency of a non-recursive translation, these calls should be replaced by in-line code. Overflow/underflow tests can be eliminated when it is known that we are operating within array bounds.

27
27/28 class BinarySearchApp { public static void main(String[] args) { int maxSize = 100; // array size ordArray arr; // reference to array arr = new ordArray(maxSize); // create the array arr.insert(72); // insert items arr.insert(90); arr.insert(45); arr.insert(126); arr.insert(54); arr.insert(99); arr.insert(144); arr.insert(27); arr.insert(135); arr.insert(81); arr.insert(18); arr.insert(108); arr.insert(9); arr.insert(117); arr.insert(63); arr.insert(36); arr.display(); // display array int searchKey = 27; // search for item if( arr.find(searchKey) != arr.size() ) System.out.println("Found " + searchKey); else System.out.println("Can't find " + searchKey); } // end main() } // end class BinarySearchApp

28
28/28 public void insert(long value) // put element into array { int j; for(j=0; j

29
29/28 // binarySearch.java class ordArray { private long[] a; // ref to array a private int nElems; // number of data items //----------------------------------------------------------- public ordArray(int max) // constructor { a = new long[max]; // create array nElems = 0; } //----------------------------------------------------------- public int size() { return nElems; } //----------------------------------------------------------- public int find(long searchKey) { return recFind(searchKey, 0, nElems-1); } //----------------------------------------------------------- private int recFind(long searchKey, int lowerBound, int upperBound) { int curIn; curIn = (lowerBound + upperBound ) / 2; if(a[curIn]==searchKey) return curIn; // found it else if(lowerBound > upperBound) exception conditions (found it and return nElems; // can't find itnot found. else // divide range { if(a[curIn] < searchKey) // it's in upper half return recFind(searchKey, curIn+1, upperBound); recursive call Notice how short the routine is !!!!! else // it's in lower half return recFind(searchKey, lowerBound, curIn-1); recursive call } // end else divide range } // end recFind() //-----------------------------------------------------------

Similar presentations

© 2017 SlidePlayer.com Inc.

All rights reserved.

Ads by Google