Download presentation
Presentation is loading. Please wait.
Published byRosemary Morgan Modified over 9 years ago
1
Recursion Self-Referencing Functions
2
Problem # 1 Write a function that, given n, computes n! n! == 1 2... (n-1) n n! == 1 2 ... (n-1) nExample: 5! == 1 2 3 4 5 == 120 5! == 1 2 3 4 5 == 120Specification: Receive:n, an integer. Precondition: n >= 0 (0! == 1 && 1! == 1). Return: n!, a double (to avoid integer overflow).
3
Preliminary Analysis At first glance, this is a counting problem, so we could solve it with a for loop: double Factorial(int n) { double result = 1.0; for (int i = 2; i <= n; i++) result *= i; return result; } But let’s instead learn a different approach...
4
Analysis Consider: n! == 1 2... (n-1) n Consider: n! == 1 2 ... (n-1) n so:(n-1)!== 1 2... (n-1) so:(n-1)!== 1 2 ... (n-1) subsituting: n!== (n-1)! n subsituting: n!== (n-1)! n We have defined the ! function in terms of itself. Historically, this is how the function was defined before computers (and for-loops) existed.
5
Recursion A function that is defined in terms of itself is called self- referential, or recursive. Recursive functions are designed in a 3-step process: 1. Identify a base case -- an instance of the problem whose solution is trivial. Example: The factorial function has two base cases: if n == 0: n! == 1 if n == 1: n! == 1
6
Induction Step 2. Identify an induction step -- a means of solving the non-trivial (or “big”) instances of the problem using one or more “smaller” instances of the problem. Example: In the factorial problem, we solve the “big” problem using a “smaller” version of the problem: n! == n n! == (n-1)! n 3. Form an algorithm from the base case and induction step.
7
Algorithm // Factorial(n) 0. Receive n. 1. If n > 1: Return Factorial(n-1) * n. Else Return 1.
8
Discussion The scope of a function begins at its prototype (or heading) and ends at the end of the file. Since the body of a function lies within its scope, nothing prevents a function from calling itself.
9
Coding /* Factorial(n), defined recursively *... */ double Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1.0; }
10
Behavior Suppose the function is called with n == 4. int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; }
11
Behavior The function starts executing, with n == 4. int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4)
12
Behavior The if executes, and n (4) > 1,... int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4)
13
Behavior and computing the return-value calls Factorial(3). int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4)
14
Behavior This begins a new execution, in which n == 3. int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4) n 3 return ? Factorial(3)
15
Behavior Its if executes, and n (3) > 1,... int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4) n 3 return ? Factorial(3)
16
Behavior and computing its return-value calls Factorial(2). int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4) n 3 return ? Factorial(3)
17
Behavior This begins a new execution, in which n == 2. int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4) n 3 return ? Factorial(3) n 2 return ? Factorial(2)
18
Behavior Its if executes, and n (2) > 1,... int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4) n 3 return ? Factorial(3) n 2 return ? Factorial(2)
19
Behavior and computing its return-value calls Factorial(1). int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4) n 3 return ? Factorial(3) n 2 return ? Factorial(2)
20
Behavior This begins a new execution, in which n == 1. int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4) n 3 return ? Factorial(3) n 2 return ? Factorial(2) n 1 return ? Factorial(1)
21
Behavior The if executes, and the condition n > 1 is false,... int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4) n 3 return ? Factorial(3) n 2 return ? Factorial(2) n 1 return ? Factorial(1)
22
Behavior so its return-value is computed as 1 (the base case) int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4) n 3 return ? Factorial(3) n 2 return ? Factorial(2) n 1 return 1 Factorial(1)
23
Behavior Factorial(1) terminates, returning 1 to Factorial(2). int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4) n 3 return ? Factorial(3) n 2 return ? Factorial(2) n 1 return 1 = 1 * 2
24
Behavior Factorial(2) resumes, computing its return-value: int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4) n 3 return ? Factorial(3) n 2 return 2 Factorial(2) = 1 * 2
25
Behavior Factorial(2) terminates, returning 2 to Factorial(3): int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4) n 3 return ? Factorial(3) n 2 return 2 = 2 * 3
26
Behavior Factorial(3) resumes, and computes its return-value: int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4) n 3 return 6 Factorial(3) = 2 * 3
27
Behavior Factorial(3) terminates, returning 6 to Factorial(4): int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return ? Factorial(4) n 3 return 6 = 6 * 4
28
Behavior Factorial(4) resumes, and computes its return-value: int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return 24 Factorial(4) = 6 * 4
29
Behavior Factorial(4) terminates, returning 24 to its caller. int Factorial(int n) { if (n > 1) return Factorial(n-1) * n; else return 1; } n 4 return 24 Factorial(4)
30
Discussion If we time the for-loop version and the recursive version, the for-loop version will usually win, because the overhead of a function call is far more time-consuming than the time to execute a loop. However, there are problems where the recursive solution is more efficient than a corresponding loop-based solution.
31
Problem # 2 Consider the exponentiation problem: Given two values x and n, compute x n. Example: 3 3 == 27 Specification: Receive: x, n, two numbers. Precondition: n >= 0 (for simplicity) && n is a whole number. Return: x raised to the power n.
32
Analysis The loop-based solution requires n “steps” (trips through the loop) to solve the problem: The loop-based solution requires n “steps” (trips through the loop) to solve the problem: double Power(double x, int n) { double result = 1.0; for (int i = 1; i <= n; i++) result *= x; return result; } There is a faster recursive solution.
33
Analysis (Ct’d) How do we perform exponentiation recursively? Base case: n == 0 Return 1.0. Return 1.0.
34
Analysis (Ct’d) Induction step: n > 0 We might recognize that x n == x x... x x // n factors of x x n == x x ... x x // n factors of xand x n-1 == x x... x // n-1 factors of x x n-1 == x x ... x // n-1 factors of x and so perform a substitution: Return Power(x, n-1) * x. Return Power(x, n-1) * x. However, this requires n “steps” (recursive calls), which is no better than the loop version. However, this requires n “steps” (recursive calls), which is no better than the loop version.
35
Analysis (Ct’d) Induction-step: n > 0. Instead, we again begin with x n == x x... x x // n factors of x x n == x x ... x x // n factors of x but note that x n/2 == x... x // n/2 factors of x x n/2 == x ... x // n/2 factors of x and then recognize that when n is even: x n == x n/2 x n/2 x n == x n/2 x n/2 while when n is odd: x n == x x n/2 x n/2 x n == x x n/2 x n/2
36
Analysis (Ct’d) Induction step: n > 0. We can avoid computing x n/2 twice by doing it once, storing the result, and using the stored value: a. Compute partialResult = Power(x, n/2); b. If x is even: b. If x is even: Return partialResult * partialResult. Else Return x * partialResult * partialResult. End if. This version is significantly faster than the loop-based version of the function, as n gets larger.
37
Algorithm 0. Receive x, n. 1. If n == 0: Return 1.0; Else a. Compute partialResult = Power(x, n/2); b. if n is even: Return partialResult * partialResult; else else Return x * partialResult * partialResult; end if. end if.
38
Coding We can then code this algorithm as follows: double Power(double x, int n) { if (n == 0) return 1.0; else { double partialResult = Power(x, n/2); if (n % 2 == 0) return partialResult * partialResult; else return x * partialResult * partialResult; } } which is quite simple, all things considered...
39
How Much Faster? How many “steps” (recursive calls) in this version? Power(x, i)i StepsSteps (loop version) (this version) (loop version) (this version) Power(x, 0)0 0 0 Power(x, 1)1 1 1 Power(x, 2)2 2 2 Power(x, 4)4 4 3 Power(x, 8)8 8 4 Power(x, 16)16 16 5 Power(x, 32)32 32 6... Power(x, n)n n Power(x, n)n n log 2 (n)+1
40
How Much Faster? (Ct’d) The larger n is, the better this version performs: Power(x, i)i StepsSteps (loop version) (this version) (loop version) (this version) Power(x, 1024)1024 1024 11 Power(x, 2048)2048 2048 12 Power(x, 4096)4096 4096 13 Power(x, 8192)8192 8192 14... The obvious way to solve a problem may not be the most efficient way!
41
Summary A function that is defined in terms of itself is called a recursive function. To solve a problem recursively, you must be able to identify a base case, and an induction step. Determining how efficiently an algorithm solves a problem is an area of computer science known as analysis of algorithms. Analysis of algorithms measures the time of an algorithm in terms of abstract “steps”, as opposed to specific statements.
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.