Recursion
When proving an operation implementation is correct, the verifier has:
- Preconditions: assume true at the beginning of the method (initial state(s)) - Post Conditions (Ensures/Goal): what you must prove to be true at the end of the method (final state(s))
When proving a call of an operation is correct:
- Preconditions: prove that the input(s) to the operation is(are) correct before calling the operation - Post Conditions: assume operation's post conditions are true after the call
• How do we know a recursive method works? - What type of proof strategy is recursion similar to?
- Static analysis (static) - Testing (dynamic) - Formal verification (static) • Formal Verification - Correctness proofs using principles learned in Discrete Math • Shows correctness of the algorithm on all valid inputs induction
Write a recursive method generateBinary that accepts an integer and returns that number's representation in binary (base 2). - Example: generateBinary (7) returns 111 - Example: generateBinary (12) returns 1100 - Example: generateBinary (42) returns 101010
// Prints the given integer's binary representation. // Precondition: n >= 0 public static String generateBinary(int n) { if (n < 2) { // base case; same as base 10 return "" + n; } else { // recursive case; break number apart return generateBinary(n / 2) + generateBinary(n % 2); } }
• Write a recursive method pow accepts an integer base and exponent and returns the base raised to that exponent. - Example: pow(3, 4) returns 81 - Solve the problem recursively and without using loops.
// Returns base ^ exponent. // Precondition: exponent >= 0 public static int pow(int base, int exponent) { if (exponent == 0) { // base case; any number to 0th power is 1 return 1; } else { // recursive case: x^y = x * x^(y-1) return base * pow(base, exponent - 1); } }
• Notice the following mathematical property: 312 = 531441 = 9^6 = (3^2)^6 531441 = (9^2)^3 = ((3^2)^2)^3 - When does this "trick" work? - How can we incorporate this optimization into our pow method? - What is the benefit of this trick if the method already works?
// Returns base ^ exponent. // Precondition: exponent >= 0 public static int pow(int base, int exponent) { if (exponent == 0) { // base case; any number to 0th power is 1 return 1; } else if (exponent % 2 == 0) { // recursive case 1: x^y = (x^2)^(y/2) return pow(base * base, exponent / 2); } else { // recursive case 2: x^y = x * x^(y-1) return base * pow(base, exponent - 1); } }
• Consider the following recursive method: public static int mystery2(int n) { if (n < 10) { return (10 * n) + n; } else { int a = mystery2(n / 10); int b = mystery2(n % 10); return (100 * a) + b; } } - What is the result of the following call? mystery2(348)
334488
• Consider the following recursive method: public static int mystery(int n) { if (n < 10) { return n; } else { int a = n / 10; int b = n % 10; return mystery(a + b); } } - What is the result of the following call? mystery(648)
9
Method of Proof by Mathematical Induction
Consider a statement of the form, "For all integers, n>=a, a property P(n) is true." To prove such a statement, perform the following steps: Step 1 (basis step): Show that the property is true for n=a. Step 2 (inductive step): Show that for all integers m>=a, if the property is true for n=m then it is true for n=m+1. To perform this step, suppose that the property is true for n=m, where m is any particular but arbitrarily chosen integer with m>=a. [This supposition is called the inductive hypothesis.] Then show that the property is true for n=m+1.
Constructor/method Description
File(String) creates File object representing file with given name canRead() returns whether file is able to be read delete() removes file from disk exists() whether this file exists on disk getName() returns file's name isDirectory() returns whether this object represents a directory length() returns number of bytes in file listFiles() returns a File[] representing files in this directory renameTo(File) changes name of file
// Precondition: j >= 0 // Goal/Ensures: // result = j + k int sum(int j, int k) { if (j == 0) { return k; } else { int r = sum(j - 1, k); return r + 1; } }
For all integers j>=0, sum(j, k) = j+k. Step 1 (basis step): Show that the property is true for j=0. Assume j=0. We must show that sum(0, k) = 0 +k. Based on the code, sum(0, k) returns k. k = 0 + k Therefore true for j = 0. Step 2 (inductive step): Show that for all integers m>=0, if the property is true for j=m then it is true for j=m+1. suppose that the property is true for j=m, where j is any particular but arbitrarily chosen integer with m>=0. sum(m, k) = m+k Then show that the property is true for j=m+1. [sum(m+1, k) = (m+1) + k] sum(m+1, k) = sum(m, k) + 1
• The real, even simpler, base case is an n of 0, not 1: - Recursion Zen:
public static void printStars(int n) { if (n == 0) { // base case; just end the line of output System.out.println(); } else { // recursive case; print one more star System.out.print("*"); printStars(n - 1); } } The art of properly identifying the best set of cases for a recursive algorithm and expressing them elegantly
File objects
• A File object (from the java.io package) represents a file or directory on the disk.
Recursion and cases
• Every recursive algorithm involves at least 2 cases: - Base Case: A simple occurrence that can be answered directly. - Recursive Case: A more complex occurrence of the problem that cannot be directly answered, but can instead be described in terms of smaller occurrences of the same problem. • Some recursive algorithms have more than one base or recursive case, but all have at least one of each. • A crucial part of recursive programming is identifying these cases.
Recursive algorithm
• Number of people behind me: - If there is someone behind me, ask him/her how many people are behind them. • When they respond with a value N, then I will answer N + 1. - If there is nobody behind me, I will answer 0
The idea
• Recursion is all about breaking a big problem into smaller occurrences of that same problem. - Each person can solve a small part of the problem. • What is a small version of the problem that would be easy to answer? • What information from a neighbor might help me?
Recursion
• Recursion: The definition of an operation in terms of itself. - Solving a problem using recursion depends on solving smaller occurrences of the same problem. • Recursive Programming: Writing methods that call themselves to solve problems recursively. - An equally powerful substitute for iteration (loops) - Particularly well-suited to solving certain types of problems
Public/private pairs
• We cannot vary the indentation without an extra parameter: public static void crawl(File f, String indent) { • Often the parameters we need for our recursion do not match those the client will want to pass. In these cases, we instead write a pair of methods: 1) a public, non-recursive one with the parameters the client wants: public static void crawl(File f) 2) a private, recursive one with the parameters we really need: private static void crawl(File f, String indent
• Consider the following method to print a line of * characters: // Prints a line containing the given number of stars. // Precondition: n >= 0 public static void printStars(int n) { for (int i = 0; i < n; i++) { System.out.print("*"); } System.out.println(); // end the line of output } • Write a recursive version of this method (that calls itself). - Solve the problem without using any loops.
• What are the cases to consider? - What is a very easy number of stars to print without a loop? • Condensing the recursive cases into a single case: public static void printStars(int n) { if (n == 1) { // base case; just print one star System.out.println("*"); } else { // recursive case; print one more star System.out.print("*"); printStars(n - 1); } }