Recursion
Recursion Definition: when a function invokes itself A function that invokes itself is said to be Recursive Syntax & Semantics: nothing new
Thinking Non-Recursively Divide and Conquer: A common problem solving technique: - break problem down into smaller/simpler sub-problems - solve sub-problems - combine sub-solutions into solution
Coding Example Write a function that calculates and returns the Factorial of a given integer. Definition of Factorial: factorial(n), written n! = 1 * 2 * 3 *...* n when n = 0 return 1 when n < 0 return 0 (meaning “undefined”)
Coding Example: non-recursive int fact(int n) { // strictly structured, int f; // non-recursive if (n < 0) // sub-solution 1 f = 0; else if (n == 0) // sub-solution 2 f = 1; else { // sub-solution 3 for (int i=1; i<=n; i++) f = f * i; } return f;
Coding Example: non-recursive // simplified non-recursive solution int fact(int n) { int f = 1; if (n < 0) return 0; //undefined for (int i=1; i<=n; i++) f = f * i; return f; }
Thinking Recursively Recursive Divide and Conquer: - Base Case(s): solve the simplest version(s) of the problem, including for “bad input” - Recursive Case(s): - solve a simple piece of the problem - use recursion to solve the rest of the slightly-simplified problem - combine these two sub-solutions into a solution
Coding Example Mathematical Definition of Factorial: n! = 1 when n = 0 n * (n-1)! when n > 0 undefined when n < 0 Note: mathematicians like recursive definitions!!
Coding Example: recursive // strictly structured, recursive solution int fact(int n) { int f; if (n < 0) // base case: bad input f = 0; else if (n == 0) // base case: super easy f = 1; else // recursive case: f = n * fact(n-1); // fairly easy plus return f; // simplified recursive }
Coding Example: recursive // simplified C++ recursive solution int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); }
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } fact(2) void main() { cout << fact(3); }
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } fact(1) void main() { cout << fact(3); }
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } fact(0) void main() { cout << fact(3); }
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3);
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } fact(0) = 1 void main() { cout << fact(3); }
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } 1 * 1 = 1 void main() { cout << fact(3); }
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } return 1 void main() { cout << fact(3); }
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } fact(1) = 1 void main() { cout << fact(3); }
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } 2 * 1 = 2 void main() { cout << fact(3); }
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } return 2 void main() { cout << fact(3); }
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } fact(2) = 2 void main() { cout << fact(3); }
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } 3 * 2 = 6 void main() { cout << fact(3); }
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } return 6 void main() { cout << fact(3); }
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3); fact(3)=6
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3); 6
Semantic Example: int fact(int n) { if (n < 0) return 0; if (n == 0) return 1; return (n * fact(n-1)); } void main() { cout << fact(3); 6
Why Use Recursion? Advantages: Disadvantages: - simplified (clearer) coding solutions - some programmers (mathematical thinkers) prefer it. Disadvantages: - very inefficient (memory & invocation overhead) - danger of Stack Overflow(not enough memory)
Practice Example 1: Problem Statement: Write a recursive function that calculates and returns the power of 2 of a given exponent. It should handle negative exponents and an exponent of 0. ex: 23=8 2-2 = 1/22 = ¼ = 0.25 20= 1
Practice Example 1: Design: - name: pow2() - given (argument): integer exponent - returns a float (to handle negative exponents) - D&C: how to handle negative exponents? return 1 / pow2(-exp) - Base Case: pow2(0) = 1 - Recursive Case: pow2(n) = 2 * pow2(n-1)
Practice Example 1: Implementation: strictly structured float pow2(int e) { float p; if (e < 0) p = 1.0 / pow2(-e); else if (e == 0) p = 1.0; else p = 2 * pow2(e-1); return p; }
Practice Example 1: Implementation: C++ simplified float pow2(int e) { if (e < 0) return 1.0/pow2(-e); if (e == 0) return 1.0; return (2 * pow2(e-1)); }
Practice Example 2: Problem Statement: Write a recursive function that counts and returns the number of spaces in a given string. ex: "Go Cats!" 1 "The Univ of Kentucky " 4 "" 0
Practice Example 2: Design: - name: numSpaces() - given (argument): a string - returns an int - Base Case: empty string has 0 spaces - Recursive Case: - count spaces in substring of all but first char - add 1 if first char is space, 0 otherwise
Practice Example 2: Implementation: C++ simplified int numSpaces(string s) { int ns=0; if (s.empty()) return 0; ns = numSpaces(s.substr(1,-1)); if (s[0] == ' ') ns++; return ns; } // .substr(): the -1 means ”to the end”
Vocabulary Term Definition Recursion when a function invokes itself Stack Overflow when the computer is out of memory to allocate variables and arguments.