380 likes | 525 Views
Introduction to Recursion. CSCI 3333 Data Structures. by Dr. Bun Yue Professor of Computer Science yue@uhcl.edu http://sce.uhcl.edu/yue/ 2013. Acknowledgement. Mr. Charles Moen Dr. Wei Ding Ms. Krishani Abeysekera Dr. Michael Goodrich. Recursion.
E N D
Introduction to Recursion CSCI 3333 Data Structures byDr. Bun YueProfessor of Computer Scienceyue@uhcl.eduhttp://sce.uhcl.edu/yue/2013
Acknowledgement • Mr. Charles Moen • Dr. Wei Ding • Ms. Krishani Abeysekera • Dr. Michael Goodrich
Recursion • Recursion is a way to combat complexity and solve problems. • Solving a problem by: • solving the problem for ‘trivial’ cases, and • solving the same problem, but with smaller sizes.
Recursive Definition • A recursive definition: a definition in terms of itself. Example: • 0 is a natural number. • If x is a natural number, then x+1 is a natural number. (or x is a natural number if x-1 is a natural number).
Mathematical Induction • Proof by Induction is recursive in nature. • To prove S(n) for all n = 1, 2, 3. • Show: • S(n) is true for n = 1. • If S(k) is true for all k from 1 to n-1, then S(n) is true
Recursive Methods • A recursive method may call itself. Example: factorial. Iterative definition: n! = n . (n-1) . (n-2) … 3 . 2 . 1 Recursive definition:
Factorial Implementation: Fac-1 • Found here in C++: int factorial(int number) { int temp; if (number <= 1) return 1; temp = number * factorial(number - 1); return temp; } • What do you think?
Another Implementation: Fac-2 // recursive factorial function in Java: public static int recursiveFactorial(int n) { if (n == 0) return 1; // base case else return n * recursiveFactorial(n- 1); // recursive case } // By Goodrich • What do you think?
Content of a Recursive Method • Base case(s): exit conditions • Values of the input variables (exit conditions) for which we perform no recursive calls are called base cases (there should be at least one base case). • Every possible chain of recursive calls must eventually reach a base case. • Recursive calls: recursive conditions • Calls to the current method. • Each recursive call should be defined so that it makes progress towards a base case.
return 4 * 6 = 24 final answer call recursiveFactorial ( 4 ) return 3 * 2 = 6 call recursiveFactorial ( 3 ) return 2 * 1 = 2 call recursiveFactorial ( 2 ) return 1 * 1 = 1 call recursiveFactorial ( 1 ) return 1 call recursiveFactorial ( 0 ) Visualizing Recursion Example recursion trace: • Recursion trace • A box for each recursive call • An arrow from each caller to callee • An arrow from each callee to caller showing return value
Tips on Recursive Analysis • Identify exit and recursive conditions. • Ensure that the conditions cover all input ranges.
Input Range Analysis of Fac • Fac-1 and Fac-2: n (or number) has the type of int. • Fac-2 may be caught in circularity forever for negative numbers. • This is especially serious in Java as it does not support unsigned int.
Complexity Analysis • To analyze the time complexity of a recursive routine, a recurrence relation may need to be identified and solved. • E.g. for factorial: T(0) = d T(N) = T(N-1) + c Solving by substitution: T(N) = cN + d = O(N)
Reversing an Array Algorithm ReverseArray(A, i, j): Input: An array A and nonnegative integer indices i and j Output: The reversal of the elements in A starting at index i and ending at j if i < j then Swap A[i] and A[ j] ReverseArray(A, i + 1, j - 1) end if; return;
Tips on Constructing Recursion • In creating recursive methods, it is important to define the methods in ways that facilitate recursion. • This sometimes requires that we may define additional parameters that are passed to the method. • For example, we defined the array reversal method as ReverseArray(A, i, j), not ReverseArray(A).
Implementation in Java public static void arrayReversal (int[] a) { arrayReversalWithRange(a, 0, a.length-1); } private static void arrayReversalWithRange (int[] a, int i, int j) { if (i < j) { int temp = a[i]; a[i] = a[j]; a[j] = temp; arrayReversalWithRange(a,i+1,j-1); } }
Calling arrayReversal public static void main (String args[]) { int b[] = {1,2,3,4,5,6,7}; arrayReversal(b); for (int i=0; i<b.length; i++) { System.out.println(b[i] + " "); } }
Computing Powers • The power function, p(x,n)=xn, can be defined recursively: • This leads to an power function that runs in linear time (for we make n recursive calls). • We can do better than this, however.
Recursive Squaring • We can derive a more efficient linearly recursive algorithm by using repeated squaring: • For example, 24= 2(4/2)2 = (24/2)2 = (22)2 = 42 = 16 25= 21+(4/2)2 = 2(24/2)2 = 2(22)2 = 2(42) = 32 26= 2(6/ 2)2 = (26/2)2 = (23)2 = 82 = 64 27= 21+(6/2)2 = 2(26/2)2 = 2(23)2 = 2(82) = 128.
A Recursive Squaring Method Algorithm Power(x, n): Input: A number x and integer n Output: The value xn if n = 0 then return 1 if n is odd then y = Power(x, (n - 1)/ 2) return x · y · y else y = Power(x, n/ 2) return y · y
Analyzing the Recursive Squaring Method Algorithm Power(x, n): Input: A number x and integer n Output: The value xn if n = 0 then return 1 if n is odd then y = Power(x, (n - 1)/ 2) return x · y · y else y = Power(x, n/2) return y · y Each time we make a recursive call we halve the value of n; hence, we make log n recursive calls. That is, this method runs in O(log n) time. It is important that we used a variable twice here rather than calling the method twice.
Further Analysis • Need to limit the range of n, or. • Add the recursive condition: if n < 0 return 1 / Power(x, -n);
Tail Recursion 1 • Tail recursion occurs when a linearly recursive method makes its recursive call as its last step. • The array reversal method is an example. • Such methods can easily be converted to non-recursive methods (which saves on some resources), often by the compiler and runtime system.
Tail Recursion Example Algorithm IterativeReverseArray(A, i, j ): Input: An array A and nonnegative integer indices i and j Output: The reversal of the elements in A starting at index i and ending at j while i < j do Swap A[i ] and A[ j ] i = i + 1 j = j - 1 return
Tail Recursion 2 • Tips: for linear recursion, write your method using tail recursion. • Example: Fac-1 does not use tail recursion. Fac-2 does.
Binary Recursion • Binary recursion occurs whenever there are two recursive calls for each non-base case. • Example: the DrawTicks method for drawing ticks on an English ruler.
A Binary Recursive Method for Drawing Ticks // draw a tick with no label public static void drawOneTick(int tickLength) { drawOneTick(tickLength, - 1); } // draw one tick public static void drawOneTick(int tickLength, int tickLabel) { for (int i = 0; i< tickLength; i++) System.out.print("-"); if (tickLabel >= 0) System.out.print(" " + tickLabel); System.out.print("\n"); } public static void drawTicks(int tickLength) { // draw ticks of given length if (tickLength > 0) { // stop when length drops to 0 drawTicks(tickLength- 1); // recursively draw left ticks drawOneTick(tickLength); // draw center tick drawTicks(tickLength- 1); // recursively draw right ticks } } public static void drawRuler(int nInches, int majorLength) { // draw ruler drawOneTick(majorLength, 0); // draw tick 0 and its label for (int i = 1; i <= nInches; i++) { drawTicks(majorLength- 1); // draw ticks for this inch drawOneTick(majorLength, i); // draw tick i and its label } } Note the two recursive calls
Binary Recursion • Binary Recursions are expensive. • If possible, convert it to linear recursion.
Fibonacci Numbers • Fibonacci numbers are defined recursively: F0= 0 F1= 1 Fi= Fi-1+ Fi-2 for i > 1.
Recursive Fibonacci Algorithm Algorithm BinaryFib(k): Input: Nonnegative integer k Output: The kth Fibonacci number Fk if k = 1 then return k else return BinaryFib(k - 1) + BinaryFib(k - 2) • Binary recursion from Goodrich’s book. • Any problem here?
A C++ Implementation long fib(unsigned long n) { if (n <= 1) { return n; } else { return fib(n-1)+fib(n-2); } }
Fibonacci Number • Negative Fibonacci Number: • F-1 = 1, • F-2 = -1, • Fn = F(n+2)−F(n+1). • 1,-1,2,-3,5,-8,13,…
Analyzing the Binary Recursion Fibonacci Algorithm • Let nk denote number of recursive calls made by BinaryFib(k). Then • n0 = 1 • n1 = 1 • n2 = n1 + n0 + 1 = 1 + 1 + 1 = 3 • n3 = n2 + n1 + 1 = 3 + 1 + 1 = 5 • n4 = n3 + n2 + 1 = 5 + 3 + 1 = 9 • n5 = n4 + n3 + 1 = 9 + 5 + 1 = 15 • n6 = n5 + n4 + 1 = 15 + 9 + 1 = 25 • n7 = n6 + n5 + 1 = 25 + 15 + 1 = 41 • n8 = n7 + n6 + 1 = 41 + 25 + 1 = 67. • Note that the value at least doubles for every other value of nk. That is, nk > 2k/2. It is exponential!
A Better Fibonacci Algorithm • Use linear recursion instead: Algorithm LinearFibonacci(k): Input: A nonnegative integer k Output: Pair of Fibonacci numbers (Fk, Fk-1) if k = 1 then return (k, 0) else (i, j) =LinearFibonacci(k - 1) return (i +j, i) • Runs in O(k) time.
Implementation • Java and C++ do not return multiple values. • Perl’s implementation: sub fib { return (1,0) if $_[0] == 1; my ($i, $j) = fib($_[0]-1); return ($i+$j, $i); } print "fib(13): " + fib(13) + "\n";
Java implementation public static int Fab(int n) { if (n <= 2) return 1; return FabHelper(n,2,1,1); } private static int FabHelper(int n, int k, int fk, int fk_1) { if (n == k) return fk; else return FabHelper(n, k+1, fk + fk_1, fk); }