380 likes | 561 Views
CSC 221: Recursion. Recursion: Definition. Function that solves a problem by relying on itself to compute the correct solution for a smaller version of the problem Requires terminating condition : Case for which recursion is no longer needed. Recursion: Induction Basis.
E N D
Recursion: Definition • Function that solves a problem by relying on itself to compute the correct solution for a smaller version of the problem • Requires terminating condition: Case for which recursion is no longer needed
Recursion: Induction Basis • Mathematical Induction: • Prove that statement is true for first n values, given that it is true for first n-1 values • Prove that the statement is true for a base case.
Recursion: Mathematical Induction • Sum of first N positive integers is (N*(N+1)) / 2 • Base Case: • 1st positive integer 1 (1 * (1+1)) / 2 =>(1*2)/2 => 2/2 => 1 • Inductive Case: Assume true for n-1 • Sum(1..N-1) = ((N-1) * (N-1+1)) / 2 => ((N-1) * (N)) / 2) = (N2 –N)/2 • Adding N = (N2 –N)/2 + N = (N2-N)/2 + 2N/2 = (N2 + N)/2 => (N * (N+1)) / 2
Factorial Recursion • Factorial: n! = n * (n-1)! • Base Case => 0! = 1 • Smaller problem => Solving (n-1)! • Implementation: long factorial(long inputValue) { if (inputValue == 0) return 1; else return inputValue * factorial(inputValue - 1); }
Searching • We want to find whether or not an input value is in a sorted list: 8 in [1, 2, 8, 10, 15, 32, 63, 64]? 33 in [1, 2, 8, 10, 15, 32, 63, 64]?
Searching int index = 0; while (index < listSize) { if (list[index] == input) return index; index++; } return –1;
Searching • Better method: • Number of operations to find input if in the list: • Dependent on position in list • 1 operation to size of list • Number of operations to find input if not in the list: • Size of list
Searching • Better method? • Use fact that we know the list is sorted • Cut what we have to search in half each time • Compare input to middle • If input greater than middle, our value has be in the elements on the right side of the middle element • If input less than middle, our value has to be in the elements on the left side of the middle element • If input equals middle, we found the element.
Searching: Binary Search for (int left = 0, right = n –1; left <= right;) { middle =(left + right) / 2; if (input == list[middle]) return middle; else if (input < list[middle]) right = middle – 1; else left = middle + 1; } return – 1;
Searching: Binary Search 8 in [1, 2, 8, 10, 15, 32, 63, 64]? 1st iteration: Left = 0, Right = 7, Middle = 3, List[Middle] = 10 Check 8 == 10 => No, 8 < 10 2nd iteration: Left = 0, Right = 2, Middle = 1, List[Middle] = 2 Check 8 == 2 => No, 8 > 2 3rd iteration: Left = 2, Right = 2, Middle = 2 Check 8 == 8 => Yes, Found It!
Searching: Binary Search • Binary Search Method: • Number of operations to find input if in the list: • Dependent on position in list • 1 operation if middle • Log2 n operations maximum • Number of operations to find input if not in the list: • Log2 n operations maximum
Recursive Binary Search • Two requirements for recursion: • Same algorithm, smaller problem • Termination condition • Binary search? • Search in half of previous array • Stop when down to one element
Recursive Binary Search int BinarySearch(int *list, const int input, const int left, const int right) { if (left < right) { middle =(left + right) / 2; if (input == list[middle]) return middle; else if (input < list[middle]) return BinarySearch(list, input, left, middle-1); else return BinarySearch(list,input,middle+1,right); } return – 1; }
While vs Recursion • While and Recursion are essentially interchangeable • Considerations: • Efficiency • Simplification of programming • Readability/Understandability • While they are equivalent, there is not always an obvious while implementation of some functions that are easily implemented with recursion
Fibonacci Computation • Fibonacci Sequence: • 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, … • Simple definition: • Fib[0] = 1 • Fib[1] = 1 • Fib[N] = Fib(N-1) + Fib(N-2)
Recursive Fibonacci int fibonacci(int input) { if ((input == 0) || (input == 1)) return 1; else return (fibonacci(input-1) + fibonacci(input-2)); }
Iterative Fibonacci int fibonacci(int input) { int first = 1; int second = 1; int temp; for (int k = 0; k < input; k++) { temp = first; first = second; second = temp + second; } return first; }
Efficiency of Recursion • Recursion can sometimes be slower than iterative code • Two main reasons: • Program stack usage • Result generation
Types of Recursion • Linear Recursion: • 1 recursive call per function • Factorial, Binary Search examples • Tree Recursion: • 2 or more recursive calls per function • Fibonacci Example
Efficiency of Recursion • Stack Usage: • When a function is called by a program, that function is placed on the program call stack: readFile() Returns file data to be used in getData() getData() main() Returns formatted data to be printed in main()
Efficiency of Recursion • Every stack entry maintains information about the function: • Where to return to when the function completes • Storage for local variables • Pointers or copies of arguments passed in
Efficiency of Recursion • When using recursive functions, every recursive call is added to the stack and it grows fast: • Fibonacci (5) • Fibonacci (5) = Fibonacci (4) + Fibonacci(3) • Fibonacci (1) in the computation of Fibonacci(4) is the first time we don’t have to call the function again. • Stack entries take up space and require extra processing Fibonacci(1) Fibonacci(2) Fibonacci(3) Fibonacci(4) Fibonacci(5) main()
Efficiency of Recursion • Another Reason for Slowdowns [Tree Recursion] • Traditional Recursion doesn’t save answers as it executes • Fib(5) = Fib(4) + Fib(3) = Fib(3) + Fib(2) + Fib(3) = Fib(2) + Fib(1) + Fib(2) + Fib(3) = Fib(1) + Fib(0) + Fib(1) + Fib(2) + Fib(3) = Fib(1) + Fib(0) + Fib(1) + Fib(1) + Fib(0) + Fib(3) = Fib(1) + Fib(0) + Fib(1) + Fib(1) + Fib(0) + Fib(2) + Fib(1) = Fib(1) + Fib(0) + Fib(1) + Fib(1) + Fib(0) + Fib(1) + Fib(0) + Fib(1) • Solution: Dynamic programming – saving answers as you go and reusing them
Dynamic Programming • Fibonacci Problem • We know the upper bound we are solving for • Ie Fibonacci (60) = 60 different answers • Generate an array 60 long and initalize to –1 • Everytime we find a solution, fill it in in the array • Next time we look for a solution, if the value in the array for the factorial we need is not –1, use the value present.
Fibonacci Examples • Three implementations of fibonacci: • Naive recursion implementation (worst performance) • Dynamic Programming recursion implementation (better) • Iterative implementation (best)
Recursive Datastructures • Recursion is useful when datastructure is inherently recursive • Unix directory hierarchy is a tree datastructure / /home /var /usr /home/turketwh /home/turketwh/CS112 /home/turketwh/CS221
Directory Traversal • ls –R in Unix => recursive list • Potential implementation? • listDirectory(directory baseDirectory) { file[] files = getFiles(); int fileCount = getFileCount(); for (int i = 0; i < fileCount; i++) { if (files[I].type == “dir”) listDirectory(file); else listFile(file); } }
Recursive Datastructures • Will see a lot of datastructures that are recursive • Lists [Atom + Smaller List] • Trees [ Root + Subtrees] • Have simple implementations because their functionality can be defined recursively.
Towers of Hanoi Not allowed General Problem: For any number of boxes, move boxes from start peg to destination peg. Can never place a bigger box on top of a smaller box.
Towers Of Hanoi • Recursive? • 1 box from peg 1 to peg 3 1 _ _ _ _ 1 • 2 boxes from peg 1 to peg 3 1 1 2 _ _ 2 1 _ _ 1 2 _ _ 2
Towers Of Hanoi • 3 boxes from peg 1 to peg 3 1 2 2 1 1 3 _ _ 3 _ 1 3 2 1 3 2 _ _ 2 3 1 2 2 1 2 3 1 _ 3 _ _ 3 • For n boxes, • Solve the n-1 problem from start to the temp peg • Move the nth box to the destination peg • Solve the n-1 problem from the temp peg to the destination peg
Space Efficiency • Factorial(N) • Factorial(4) => = 4*Factorial(3) = 4 * 3 * Factorial(2) = 4 * 3 * 2 * Factorial(1) = 4 * 3 * 2 * 1 • Has to make a maximum of N calls before base case is reached and function returns. • N activation records will be placed on the stack. • Since the size of the input is N, this function requires memory that is linearly related to the size of the input
Space Efficiency • Fibonacci(N) – Standard recursive implementation • Fib(5) = Fib(4) + Fib(3) • For Fib(4), also puts Fib(3),Fib(2), Fib(1) on stack • For Fib(3), also puts Fib(2), Fib(1) on stack • Fib(4), Fib(3) compute separately • Space Efficiency considers maximum amount required at one time – Fib(4) branch in this case • Requires memory linearly related to input size
Tail Recursion Tail Recursion: When the results of a recursive call are not used after the recursive call within the calling function. long factorialHelper(long startValue, long inputValue) { if (startValue == 0) return 1; else if (inputValue == 1) return startValue; else return factorialHelper(startValue * (inputValue-1), inputValue - 1); } long factorial(long inputValue) { return factorialHelp(inputValue, inputValue); }
Space Efficiency: Tail Recursion • Since the results of the recursive call aren’t needed for other computations, the stack frames aren’t needed to hold partial results. • With each call, new frame replaces old frame. • The compiler handles these optimizations. • Requires constant amount of memory and is not dependent on the size of the input.
Space Efficiency: Tail Recursion factorialHelper(3,3) main() factorialHelper(6,1) Return 6 factorialHelper(6,2) Call factorialHelper(6,2) Return 6 main() Call factorialHelper(3,3) Return 6 Call main() factorialHelper(6,1) main() Without Optimization With Optimization looks like this: (each call of fh is done when it calls the next, so take it off the stack)
Recursion • Key Ideas • Decomposition: Solve smaller problem(s) and combine answers • Tends to allow for very simple writing of code • Used naively, may lead to • Significant stack usage • Repeated computations • Have touched on methodologies to help work around those issues: • Tail Recursion • Storing answers (‘dynamic programming’) • Suggests a new technique may be needed for computing number of operations for algorithm to complete