1 / 47

Chapter 7 – Recursion

7.1 Recursive Thinking 7.2 Recursive Definitions of Mathematical Formulas 7.3 Recursive Search. Chapter 7 – Recursion. Attendance Quiz #22. Tip #23: Class or Function?.

pcinnamon
Download Presentation

Chapter 7 – Recursion

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 7.1 Recursive Thinking 7.2 Recursive Definitions of Mathematical Formulas 7.3 Recursive Search Chapter 7 – Recursion

  2. Attendance Quiz #22 Recursion (23)

  3. Tip #23: Class or Function? Recursion (23) • Its not about class or function - its about being able to write more efficient and reusable code in developing powerful apps what would be too complicated to write without classes. • OOP is generally about tying together state and behavior. • Classes group attributes (state) and behavior (methods) and stating that only the methods (encapsulation) can act on the state. • In OOP, the methods are responsible for implementing the behavior of the class. • The methods participate in the encapsulation of state (freeing clients from the implementation detail) and in the preservation of the class invariants (statements about the class state that hold true from its birth to its death). • Classes support polymorphism – the essence of OOP. • Using OO over functions or older styles in a larger program is for portability. • Easier, quicker, and more securer to pull out the information from a class. • For a beginner it may seem you don't need classes • for the little things, maybe… • But even fairly simple programs could be vastly improved with classes - serious projects almost can't happen without them.

  4. 7.1 Recursive Thinking Steps to Design a Recursive Algorithm Proving That a Recursive Function Is Correct Tracing a Recursive Function The Stack and Activation Frames 7.1, pgs. 404-411

  5. The Stack and Activation Frames Recursion (23) • C++ maintains a stack on which it saves new information in the form of an activation frame. • The activation frame contains storage for • the return address of the calling function. • function arguments. • local variables (if any). • Whenever a new function is called (recursive or otherwise), C++ pushes a new activation frame onto the stack.

  6. Tracing a Recursive Function Recursion (23) • The process of returning from recursive calls and computing the partial results is called unwindingthe recursion. #include <iostream> using namespace std; int size(string str) { if (str == "") return 0; return 1 + size(str.substr(1)); } int main() { cout << size("ace"); return 0; } cout << size("ace"); 3 return 1 + size("ce") 2 return 1 + size("e") 1 return 1 + size("") 0

  7. Run-Time Stack - Activation Frames Recursion (23) int main() { cout << size("ace"); return 0; } int size(string str) { if (str == "") return 0; return size(str.substr(1)); }

  8. 7.2 Recursive Definitions of Mathematical Formulas Recursion Versus Iteration Tail Recursion or Last-Line Recursion Efficiency of Recursion 7.2, pgs. 412-419

  9. Recursive Mathematical Formulas Recursion (23) • Mathematicians often use recursive definitions of formulas that lead naturally to recursive algorithms • Examples include: • factorials • powers • greatest common divisors • sorts x = factorial(4) 24 return 4 * factorial(3) 6 int factorial(int n) { if (n <= 1) return 1; return (n * factorial(n – 1)); } return 3 * factorial(2) 2 return 2 * factorial(1) 1

  10. Calculating xn Recursion (23) • You can raise a number to a power that is greater than 0 by repeatedly multiplying the number by itself. • Recursive definition: xn = x  xn-1 • Base case: x0 = 1 x = power(2.0, -3) /** Recursive power function @param x: The number @param n: The exponent @return x raised to the power n */ double power(double x, int n) { if (n == 0) return 1.0; if (n > 0) return x * power(x, n – 1); else return 1.0 / power(x, -n); } 0.125 return 1.0 / power(2.0, 3) 8.0 return 2.0 * power(2.0, 2) 4.0 return 2.0 * power(2.0, 1) 2.0 return 2.0 * power(2.0, 0) 1.0

  11. Trace the call stack of the following recursive Greatest-Common-Divisor function for gcd(30, 77). /** Recursive Greatest Common Divisor function @param m The larger number (m > 0) @param n The smaller number (n > 0) @return gcd of m and n */ int gcd(int m, int n) { if (m % n == 0) return n; if (m < n) return gcd(n, m); else return gcd(n, m % n); } x = gcd(30, 77)

  12. Calculating GCD Correct? Recursion (23) • How do we verify that our algorithm is correct? • Base case correct? • The base case is “n is a divisor of m” • The solution is n (n is the greatest common divisor), which is correct • Does recursion make progress to base case? • Both arguments in each recursive call are smaller than in the previous call and • The new second argument is always smaller than the new first argument (m % n must be less than n) • Eventually a divisor will be found or the second argument will become 1 (which is a base case because it divides every integer)

  13. Recursion Downside Recursion (23) • Recursion downside • Base case never reached. • Factorial with a negative argument - throw an invalid_argument exception if n is negative. • You will eventually get a run-time error when there is no more memory available for your program to execute more function calls. • Error deep in recursion stack. • Recursion vs Iteration • There are similarities between recursion and iteration. • In iteration, a loop repetition condition determines whether to repeat the loop body or exit from the loop. • In recursion, the condition usually tests for a base case. • There is always an iterative solution to a problem that is solvable by recursion. • A recursive algorithm may be simpler than an iterative algorithm and thus easier to design, code, debug, and maintain.

  14. Tail Recursion Recursion (23) • Most of the recursive algorithms and functions so far are examples of tail recursion or last-line recursion. • In these algorithms, there is a single recursive call and it is the last line of the function, such as in the factorial function. • Straightforward process to turn a recursion function with a tail into an iterative solution: /** Recursive factorial function */ int factorial(int n) { if (n <= 1) return 1; return n * factorial(n – 1); } /** Iterative factorial function */ int factorial_iter(int n) { int result = 1; for (int k = 1; k <= n; k++) result = result * k; return result; }

  15. Fibonacci Numbers Recursion (23) f(n) = f(n-1) + f(n-2) f(0) = 1 f(1) = 1 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987… int Fibonacci(int n) { if (n < 2) return 1; /* base case */ return (Fibonacci(n-1) + Fibonacci(n-2)); }

  16. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  17. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) Fibonacci(3) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  18. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) Fibonacci(3) Fibonacci(2) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  19. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) Fibonacci(3) Fibonacci(2) Fibonacci(1) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  20. 1 Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) Fibonacci(3) Fibonacci(2) Fibonacci(1) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  21. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) Fibonacci(3) Fibonacci(2) 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  22. 1 Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) Fibonacci(3) Fibonacci(2) 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  23. 1 Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) Fibonacci(3) Fibonacci(2) + 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  24. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) Fibonacci(3) 2 Fibonacci(2) + 1 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  25. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) Fibonacci(3) 2 Fibonacci(2) Fibonacci(1) + 1 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  26. 1 Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) Fibonacci(3) 2 Fibonacci(2) Fibonacci(1) + 1 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  27. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) Fibonacci(3) + 2 1 Fibonacci(2) Fibonacci(1) + 1 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  28. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) 3 Fibonacci(3) + 2 1 Fibonacci(2) Fibonacci(1) + 1 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  29. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) 3 Fibonacci(3) Fibonacci(2) + 2 1 Fibonacci(2) Fibonacci(1) + 1 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  30. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) 3 Fibonacci(3) Fibonacci(2) + 2 1 Fibonacci(2) Fibonacci(1) Fibonacci(1) + 1 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  31. 1 Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) 3 Fibonacci(3) Fibonacci(2) + 2 1 Fibonacci(2) Fibonacci(1) Fibonacci(1) + 1 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  32. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) 3 Fibonacci(3) Fibonacci(2) + 1 2 1 Fibonacci(2) Fibonacci(1) Fibonacci(1) Fibonacci(0) + 1 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  33. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) 3 Fibonacci(3) Fibonacci(2) + 1 1 2 1 Fibonacci(2) Fibonacci(1) Fibonacci(1) Fibonacci(0) + 1 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  34. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) 3 Fibonacci(3) Fibonacci(2) + + 1 1 2 1 Fibonacci(2) Fibonacci(1) Fibonacci(1) Fibonacci(0) + 1 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  35. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) 2 3 Fibonacci(3) Fibonacci(2) + + 1 1 2 1 Fibonacci(2) Fibonacci(1) Fibonacci(1) Fibonacci(0) + 1 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  36. Call Tree for Fibonacci Recursion (23) main() Fibonacci(4) + 2 3 Fibonacci(3) Fibonacci(2) + + 1 1 2 1 Fibonacci(2) Fibonacci(1) Fibonacci(1) Fibonacci(0) + 1 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  37. Call Tree for Fibonacci Recursion (23) main() 5 Fibonacci(4) + 2 3 Fibonacci(3) Fibonacci(2) + + 1 1 2 1 Fibonacci(2) Fibonacci(1) Fibonacci(1) Fibonacci(0) + 1 1 Fibonacci(1) Fibonacci(0) int Fibonacci(int n) { if (n < 2) return 1; return (Fibonacci(n-1) + Fibonacci(n-2)); } int main() { cout << "Fibonacci(4) = " << Fibonacci(4); }

  38. Fibonacci Numbers O(n) Recursion (23) • Because of the redundant function calls, the time required to calculate fibonacci(n) increases exponentially with n. • If n is 100, there are approximately 2100 activation frames (1030). • If you could process one million activation frames per second, it would still take 1024 seconds (3  1016 years) to compute fibonacci(100). /** Recursive O(n) function to calculate Fibonacci numbers @paramfib_current The current Fibonacci number @paramfib_previous The previous Fibonacci number @param n The count of Fibonacci numbers left to calculate @return The value of the Fibonacci number calculated so far */ int fibo(int fib_current, int fib_previous, int n) { if (n == 1) return fib_current; return fibo(fib_current + fib_previous, fib_current, n – 1); }

  39. Fibonacci Numbers O(n) fibonacci(4) 5 Recursion (23) fibo(1, 1, 4) 5 /** Fibonacci sequence @paramfib_current Current # @paramfib_previous Previous # @param n # left to calculate @return Current Fibonacci value */ int fibo(int current, int previous, int n) { if (n < 2) return current; return fibo(current + previous, current, n - 1); } int fibonacci(int n) { return fibo(1, 1, n); } int main() { cout << "Fibonacci(4) = " << fibonacci(4); } fibo(2, 1, 3) 5 fibo(3, 2, 2) 5 fibo(5, 3, 1)

  40. 7.3 Recursive Search Design of a Recursive Linear Search Algorithm Implementation of Linear Search Design of Binary Search Algorithm Efficiency of Binary Search Implementation of Binary Search Testing Binary Search 7.3, pgs. 420-426

  41. Recursive vector Search Recursion (23) • Searching a vector can be accomplished using recursion. • The simplest way to search is a linear search: • Examine one element at a time starting with the first element or the last element to see whether it matches the target. • On average, approximately n/2 elements are examined to find the target in a linear search. • If the target is not in the vector, all n elements are examined. • Base cases for recursive search: • Empty vector, target can not be found; result is -1. • First element of the vector matches the target; result is the subscript of first element. • The recursive step searches the rest of the vector, excluding the first element. • What is the order of complexity? O(n).

  42. Recursive vector Search Recursion (23) /** Recursive linear search function @param items The vector being searched @param target The item being searched for @param first The position of the current first element @return The subscript of target if found; otherwise -1 */ template<typename T> int linear_search(const vector<T>& items, const T& target, size_t first) { if (first == items.size()) return -1; if (target == items[first]) return first; return linear_search(items, target, first + 1); } /** Wrapper for recursive linear search function */ template<typename T> int linear_search(const std::vector<T>& items, const T& target) { return linear_search(items, target, 0); }

  43. Design of a Binary Search Algorithm Recursion (23) • A binary search can be performed only on an array or vector that has been sorted. • Base cases: • The range of the vector is empty. • The element being examined matches the target. • Rather than looking at the first element, a binary search compares the middle element for a match with the target. • If the middle element does not match the target, a binary search excludes the half of the vector (within which the target would not be found.) O(log n). • What is the order of complexity?

  44. Binary Search Algorithm Recursion (23)

  45. Efficiency of Binary Search Recursion (23) • At each recursive call we eliminate half the vector elements from consideration, making a binary search O(log n). • A vector of size 16 would search vectors of length 16, 8, 4, 2, and 1, making 5 probes in the worst case. • 16 = 24 • 5 = log2 16 + 1 • A doubled vector size would require only 6 probes in the worst case. • 32 = 25 • 6 = log2 32 + 1 • A vector with 32,768 elements requires only 16 probes! • A vector of 65,536 elements increases the number of required probes to 17.

  46. Recursive Binary Search Recursion (23) template <typename T> int binary_search(const vector<T>& items, int first, int last, const T& target) { if (first > last) return -1; // (base case #1) size_t middle = (first + last) / 2; // next probe if (target == items[middle]) return middle; //success (base case #2) if (target < items[middle]) return binary_search(items, first, middle - 1, target); else return binary_search(items, middle + 1, last, target); } /** Wrapper for recursive binary search function */ template<typename T> int binary_search(const vector<T>& items, const T& target) { return binary_search(items, 0, items.size() - 1, target); }

More Related