290 likes | 306 Views
Explore the benefits of recursion in power calculation and compare it to iterative solutions, highlighting the efficiency of recursion.
E N D
Recursionyet anotherlookTo recurse is divine, to iterate is human
“Do I Have to Use Recursion?” public static double pow(double value, int exponent){ if (exponent == 0) return 1D; else if (exponent == 1) return value; else { exponent = exponent - 1; return value * pow (value, exponent); } } How many would have preferred to do this with a “for loop” structure or some other iterative solution? How many think we can make our recursive method even faster than iteration?
Nota Bene Our power function works through brute force recursion. 28 = 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 But we can rewrite this brute force solution into two equal halves: 28 = 24 * 24 and 24 = 22 * 22 and 22 = 21 * 21 and anything to the power 1 is itself!
And here's the cool part... 28 = 24 * 24 Since these are the same we don't have to calculate them both!
AHA! So only THREE multiplication operations have to take place: 28 = 24 * 24 24 = 22 * 22 22 = 21 * 21 So the trick is knowing that 28 can be solved by dividing the problem in half and using the result twice!
"But wait," I hear you say! You picked an even power of 2. What about our friends the odd numbers? Okay we can do odds like this: 2odd = 2 * 2 (odd-1)
"But wait," I hear you say! You picked a power of 2. Let’s see an odd one! Okay, how about 221 221 = 2 * 220 (The odd number trick) 220 = 210 * 210 210 = 25 * 25 25 = 2 * 24 24 = 22 * 22 22 = 21 * 21
"But wait," I hear you say! You picked a power of 2. That's a no brainer! Okay how about 221 221 = 2 * 220 (The odd number trick) 220 = 210 * 210 210 = 25 * 25 25 = 2 * 24 24 = 22 * 22 22 = 21 * 21 That's 6 multiplications instead of 20 and it gets more dramatic as the exponent increases
The Recursive Insight If the exponent is even, we can divide and conquer so it can be solved in halves. If the exponent is odd, we can subtract one, remembering to multiply the end result one last time. We begin to develop a formula: Pow(x, e) = 1, where e == 0 Pow(x, e) = x, where e == 1 Pow(x, e) = Pow(x, e/2) * Pow(x,e/2), where e is even Pow(x, e) = x * Pow(x, e-1), where e > 1, and is odd
Solution #2 public static double pow (double value, int exponent){ if (exponent == 0) return 1D; else if (exponent == 1) return value; } We have the same base termination conditions as before, right?
Solution #2 This little gem determines if a number is odd or even. public static double pow (double value, int exponent){ if (exponent == 0) return 1D; else if (exponent == 1) return value; else if (exponent % 2 == 0) { }
Solution #2 public static double pow (double value, int exponent){ if (exponent == 0) return 1D; else if (exponent == 1) return value; else if (exponent % 2 == 0) { exponent = exponent / 2; } We next divide the exponent in half.
Solution #2 public static double pow (double value, int exponent){ if (exponent == 0) return 1D; else if (exponent == 1) return value; else if (exponent % 2 == 0) { exponent = exponent / 2; double half = pow (value, exponent); } We recurse to find that half of the brute force multiplication.
Solution #2 public static double pow (double value, int exponent){ if (exponent == 0) return 1D; else if (exponent == 1) return value; else if (exponent % 2 == 0) { exponent = exponent / 2; double half = pow (value, exponent); return half * half; } And return the two halves of the equation multiplied by themselves
Solution #2 public static double pow (double value, int exponent){ if (exponent == 0) return 1D; else if (exponent == 1) return value; else if (exponent % 2 == 0) { exponent = exponent / 2; double half = pow (value, exponent); return half * half; } else { exponent = exponent - 1; } If the exponent is odd, we have to reduce it by one . . .
Solution #2 public static double pow (double value, int exponent){ if (exponent == 0) return 1D; else if (exponent == 1) return value; else if (exponent % 2 == 0) { exponent = exponent / 2; int half = pow (value, exponent); return half * half; } else { exponent = exponent - 1; double oneless = pow (value, exponent); } And now the exponent is even, so we can just recurse to solve that portion of the equation.
Solution #2 public static double pow (double value, int exponent){ if (exponent == 0) return 1D; else if (exponent == 1) return value; else if (exponent % 2 == 0) { exponent = exponent / 2; int half = pow (value, exponent); return half * half; } else { exponent = exponent - 1; double oneless = pow (value, exponent); return oneless * value; } } We remember to multiply the value returned by the original value, since we reduced the exponent by one.
Recursion vs. Iteration: Those of you who voted for an iterative solution are likely going to produce: O(N) In a Dickensian world, you would be fired for this. While those of you who stuck it out with recursion are now looking at: O(log2n) For that, you deserve a raise!
The Cost of Recursion The stack is important – it’s no joke. Computer hardware, it’s a fact of life. We have to live with it.
The Cost of Recursion public class FibTest { public static int fib (int num) { if (num == 0) return 0; else if (num == 1) return 1; else return fib(num-1) + fib(num-2); } public static void main(String[] args) { for (int i=0; i < 10; i++) System.out.println (fib(i)); } }// FibTest public class FibTest { public static int fib (int num) { if (num == 0) return 0; else if (num == 1) return 1; else return fib(num-1) + fib(num-2); } public static void main(String[] args) { for (int i=0; i < 10; i++) System.out.println (fib(i)); } }// FibTest public class FibTest { public static int fib (int num) { if (num == 0) return 0; else if (num == 1) return 1; else return fib(num-1) + fib(num-2); } public static void main(String[] args) { for (int i=0; i < 10; i++) System.out.println (fib(i)); } }// FibTest Each time you call a method, a new “activation frame” is created. This frame has unique copies of the parameters (Java has IN parameters), and unique copies of any local variables.
Recursion Review Recursion is defining a program in such a way that it may call itself. Each recursive call creates a new stack frame stack height time Central to this understanding was the notion of a stack of successive calls.
fact (0) 1 2 1 1 6 fact (1) 1 * 1 * fact (2) 2 * 2 * 2 * fact (3) 3 * 3 * 3 * 3 * 24 fact (4) 4 * 4 * 4 * 4 * 4 * Example public int fact ( int num ){ if (num == 0) return 1; else return num * ( fact (num -1) ); } Let’s trace this for 4!
fact (0) 1 fact (1) 1 * 1 * fact (2) 2 * 2 * 2 * fact (3) 3 * 3 * 3 * 3 * fact (4) 4 * 4 * 4 * 4 * 4 * Problems We don’t calculate anything until the final recursive call. We must save a copy of each call, until we reach the end and ‘unwind’ the recursive call. This is an example of augmentative (head) recursion. Memory usage grows at O(n). This gets expensive! public int fact ( int num ){ if (num == 0) return 1; else return num * ( fact (num -1) ); }
Analyzing Augmentative Recursion public int fact ( int num ){ if (num == 0) return 1; else return num * ( fact (num -1) ); } AHA! The culprit is a recursive call that returns what gets returned from a successive recursive call (which depends on what gets returned from a recursive call, etc., etc.) We leave the multiply hanging… This requires us to save each level of the stack. How can we rewrite this to avoid having to save the state of each frame?
Tail Recursion • Eliminates the one big problem with augmentative recursion: Massive memory use. • Requires a compiler/interpreter that recognizes when a module is finished executing. (i.e. the last action is a recursive call and there’s no more work to do.) • Requires a slightly different style of recursive module writing.
Tail Recursion These values are all precalculated; no need to save each frame! public int fact (int product, int count, int max){ if (count == max) return product; else { count = count + 1; product = product * count; return fact (product, count, max); } }
Tail Recursion Need to “prime the pump” with good starting values. public int fact(int num){ if (num == 0) return 1; else return fact (1, 1, num); } public int fact (int product, int count, int max){ if (count == max) return product; else { count++; product *= count; return fact (product, count, max); } } Method overloading takes place of “helper” method These values are all precalculated; no need to save each frame!
Tracing Tail Recursion public int fact(int num){ if (num == 0) return 1; else return fact (1, 1, num); } public int fact (int product, int count, int max){ if (count == max) return product; else { count++; product *= count; return fact (product, count, max); } } Each recursive call makes calculations independent of the prior recursive calls. There’s no need to save the state of each call No stack buildup! fact(4) 1 1 4 2 2 4 6 3 4 24 4 4
Recursion Roundup Tail and Augmentative recursion are nothing more than fancy terms for different structures of recursive methods. Remember, tail recursion means that the last thing that happens in a module is simply the return of a recursive call. Tail: return fact(...); Head: return n * fact(...);