Title: Recursion
1Recursion
- n General Form of Recursive methods
- n Examples of Simple Recursion
- n Hand Simulating Recursive methods
- n Proving Recursive methods Correct
- n Modeling Recursion via Onions
- n Recursive methods on Naturals/Integers
2Recursion
- Recursion is a programming technique in which a
call to a method appears in that methods body
(i.e., a method calls itself this is called
direct recursion) - Once you understand recursive methods, they are
often simpler to write than their iterative
equivalents - In modern programming languages, recursive
methods may run a bit slower (maybe 5) than
equivalent iterative methods in a typical
application, this time is insignificant (most of
the time will be taken up elsewhere anyway) - We will begin by studying the form of general
recursive methods then apply this form to
methods operating on int values and finally
apply that form to methods operating on linked
lists (where they are most useful). In both cases
we will discuss how values of these types are
recursively defined.
3Fund Raising Iteration vs. Recursion
- Problem Collect 1,000.00 for charity
- Assumption Everyone is willing to donate a penny
- Iterative Solution
- Visit 100,000 people, asking each for a penny
- Recursive Solution
- If you are asked to collect a penny, give a penny
to the person who asked you for it - Otherwise
- Visit 10 people and ask them to each raise 1/10th
of the amount of money that you have been asked
to raise - Collect the money that they give you and combine
it into one bag - Give it to the person who asked you to collect
the money
4General Form of Recursive methods
- Solve(Problem)
-
- if (Problem is minimal/not decomposable a base
case) - solve Problem directly i.e., without
recursion - else
- (1) Decompose Problem into one or more
similar, strictly smaller subproblems
SP1, SP2, ... , SPN - (2) Recursively call Solve (this method) on
each subproblem Solve(SP1),
Solve(SP2),..., Solve(SPN) - (3) Combine the solutions to these
subproblems into a solution that solves
the original Problem -
5factorial In Mathematics and Java
Example using Definition 4! 4 3! 4 3
2! 4 3 2 1! 4 3 2 1 0!
4 3 2 1 1
- 1 if N 0
- N!
- N(N-1)! if N gt 0
-
- int factorial (int n)
-
- if (n 0) //Non-decomposable
- return 1
- else
- int subN n-1 //Decompose
- int subFact factorial(subN) //Solve
Subproblem - return n subFact //Combine
-
6Simplified and Iterative factorial
- int factorial (int n)
-
- if (n 0)
- return 1
- else
- return n factorial(n-1)
-
- int factorial (int n)
-
- int answer 1
- for (int i1 iltn i)
- answer i
- return answer
Here we combine the three steps in the previous
else block into a single return statement (one
expression including the decompose, solve, and
combine steps). Compare this method to the one
below it, which operates by iteration. The
iterative version requires the declaration of two
variables and the use of state change operators
(which are always difficult to reason about)
7pow Raising a Number to a Power
- 1 if N 0
- AN
- AAN-1 if N gt 0
-
- double pow(double a, int n)
-
- if (n 0)
- return 1.
- else
- return apow(a,n-1)
Example using Definition A4 A A3 A A
A2 A A A A1 A A A A A0
A A A A 1
Calling pow(a,n)requires exactly n multiplications
The pow in the Java Math library actually
computes its answer using logarithms and
exponentials
8Simulation of Recursive Methods
- Assume that a method is computed according to its
definition by a person in an apartment complex - That person can be called to compute an answer
once he/she computes the answer (possibly helped
by calling another person in the apartment
complex), he/she places a return call, and
reports back the answer to the person who called
him/her - Assume that each person knows the phone number of
the person living in the apartment underneath
them, who also has the same set of instructions
so any person can call the one underneath to
solve a simpler problem - The first method call initiates a sequence of
calls to people living in lower level apartments
(each person solving a simpler problem), whose
answers percolate back to the top, finally
solving the original problem
9Hand Simulation of Factorial
n return
n return
n return
n return
...
n return
10Proof Rules for Recursive Methods
- To prove that a recursive method, Solve, is
correct - Prove that Solve computes (without recursion) the
correct answer to any minimal (base case) Problem - Base cases are simple, so this should be easy
- Prove that the argument to any recursive call of
Solve, e.g. SPI, is strictly smaller (closer to
the minimal case) than Problem - The notion of strictly smaller should be easy to
understand for the recursive argument, so this
should be easy - Prove that these answers are combined to compute
the correct answer for Problem - In this proof, you may assume that each recursive
call, Solve(SPI), correctly computes its answer - The assumption makes this proof easy
- Recursion is computable induction (a math concept)
11Applying the Proof Rules
- factorial (recurring on the parameter n)
- factorial correctly returns 1 for the minimal
argument 0. - In factorial(n-1), n-1 is always closer to 0 than
n is. - Assume factorial(n-1) computes the correct
answer. - n times this value is, by definition, the correct
value of factorial(n) - pow (recurring on the parameter n)
- pow correctly returns 1 for the minimal argument
0. - In pow(a,n-1), n-1 is always closer to 0 than n
is. - Assume pow(a,n-1) computes the correct answer,
an-1. - apow(a,n-1) is aan-1 is an, the correct value
of pow(a,n)
12Proof Rules and Bad Factorials
- int factorial (int n)
-
- if (n 0)
- return 0 //0! is not 0
- else
- return nfactorial(n-1)
-
- int factorial (int n)
-
- if (n 0)
- return 1
- else
- return factorial(n1)/(n1) //n1 not closer
to 0 -
- int factorial (int n)
-
- if (n 0)
13Proof Rules and Bad Factorials (cont)
- In the first method, the wrong answer (0) is
returned for the base case since everything
depends on the base case, ultimately this method
always returns 0 - In the second method, n1 is farther away from
the base case this method will continue calling
factorial with ever larger arguments, until the
maximum int value is exceeded a runaway (or
infinite) recursion (actually, each recursive
call can take up some space, so eventually memory
is exhausted). - In the third method, the wrong answer is returned
by incorrectly combining n and the solved
subproblem this method returns one more than the
sum of all the integers from 1 to n (an
interesting method in its own right) not the
product of these values - In the first method, it always returns the wrong
answer the second method never returns an
answer, the third method returns the correct
answer only in the base case
14fpow a faster version of pow
Note if N is odd N/2 truncates so 7/2 3
- 1 if N 0
- AN B2 where B AN/2 if N gt 0 and N is Even
- AB2 where B AN/2 if N gt 0 and N is Odd
- int fpow(double a, int n)
-
- if (n 0)
- return 1.
- else
- double b fpow(a,n/2)
- if (n2 0)
- return bb
- else
- return abb
-
Calling fpow(a,n)requires between at least log2n
and at most 2log2n multiplications Compare this
complexity to calling pow(a,n)requiring n
multiplications Contemporary cryptography raises
large numbers to huge (hundred digit) powers it
needs a fast method
15Recursive Definition of Naturals
- 0 is the smallest Natural
- It is minimal, and cannot be decomposed
- z(x) is true if x is zero and false if x is
non-zero - The successor of a Natural is a Natural
- If x is a Natural, s(x) is a Natural
- The successor of x is x1
- If x is a non-0 Natural, p(x) is a Natural
- The predecessor of x is x-1
- p(0) throws an exception
- Note
- z(s(x)) is always false
- p(s(x)) x
- s(p(x)) x only if X ? 0
16Onion Model of Recursive Naturals
Every Onion is either Base Case The
core Recursive Case A layer of skin around a
smaller onion (which may be the core, or some
deeper layer) The value of an onion is the
number of layers of skin beyond the core
17Computation with Naturals
- Lets Define 3 methods that operate on Naturals
in Java - int s(int x) return x1
- int p(int x)
-
- if (x0)
- throw new IllegalArgumentException("p with
x0") - return x-1
-
- bool z(int x) return x 0
- We can define all common operators (both
arithmetic and relational) on Naturals by writing
simple recursive methods that use these three
methods
18Recursive methods on Naturals
- int sum(int a, int b)
-
- if (z(a))
- return b
- else
- return s(sum(p(a),b)) //1 (a-1 b)
-
- int sum(int a, int b)
-
- if (z(a))
- return b
- else
- return sum(p(a), s(b)) //(a-1) (b1)
-
- In both sum methods, a gets closer to 0 in
recursive calls
19Recursive methods on Naturals II
- int product(int a, int b)
-
- if (z(a))
- return 0
- else
- return sum(b,product(p(a),b)) //b (a-1)b
-
- int power(int a, int b)
-
- if (z(b))
- return 1
- else
- return product(a, power(a,p(b)))//a ab-1
-
20Recursive methods on Naturals III
- bool equal(int a, int b) //Is a b
-
- if (z(a) z(b)) //If either is 0...
- return z(a) z(b) //return whether both
are 0 - else
- return equal(p(a), p(b)) //Is (a-1) (b-1)
-
- bool less(int a, int b) //Is a lt b
-
- if (z(b)) //Nothing is lt 0
- return false
- else if (z(a)) //If b!0, a0, then altb
- return true
- else
- return less(p(a),p(b)) //Is (a-1) lt (b-1)
21Recursive methods on Naturals IV
- bool even(int a)
-
- if (z(a)) //True if 0
- return true
- else //Opposite of even(a-1)
- return !even(p(a))
-
- bool odd(int a)
-
- if (z(a)) //False if 0
- return false
- else //Opposite of odd(a-1)
- return !odd(p(a))
-
22Printing an Integers Digits
- void print (int i)
-
- if (i lt 10) //if (i gt 10)
- System.out.print(i) // print(i/10)
- else //System.out.print(i10)
- print(i/10)
- System.out.println(i10)
-
-
- print correctly prints all 1-digit values 0 - 9.
- In print(i/10), i/10 is one digit closer to 0 - 9
than i is. - Assume print (i/10) correctly prints all the
digits in i/10 in the correct order - Printing i/10 (all digits but the last) and then
printing i10 (the last digit), prints all the
digits in i in order
The code above is simplified by bottom factoring
and test reversal and noting for ilt10, i is i10
23Printing an Integers Digits in Reverse
- void printReversed (int i)
-
- if (i lt 10) //System.out.print(i10)
- System.out.print(i) //if (i gt 10)
- else // System.out.print(i/10)
- System.out.print(i10)
- printReversed(i/10)
-
-
- printReversed correctly prints all 1-digit
values 0 - 9. - In printReversed(i/10), i/10 is closer to 0-9
than i is. - Assume printReversed(i/10) correctly prints all
the digits in i/10 reversed. - Printing the last digit, i10, and then printing
the i/10 reversed prints all the digits in i
reversed.
24Converting an int to a string
- String toString (int i)
-
- if (i lt 10)
- return ""(char)(i0)
- else
- return toString(i/10) (char)(i100)
-
- toString correctly returns all 1-digit values 0
- 9. - In toString(i/10), i/10 is one digit closer to
0-9 than i is. - Assume toString(i/10) correctly returns a String
of all the digits in I/10. - Then, after appending the char equivalent of i10
at the end of this String correctly stores the
String equivalent of i.
25Synthesizing Recursive Methods
- Find the base (non-decomposable) case(s)
- Write the code that detects the base case(s) and
returns the correct answer for them without using
recursion - Assume that you can decompose all non
base-case(s) and then solve these smaller
subproblems via recursion - Choose the decomposition
- There should be some natural decomposition
- Write code that combines these solved subproblems
(often there is just one) to solve the original
problem - Typical decompositions
- Integers i - something or i/something
(digit at a time) - Linked Lists l.next
26Problems
- Write the following method public static
String reverse (String s) recursively. Choose an
appropriate base case, recursive call (hint
substring method using one parameter), and a way
to combine (hint ) solved smaller problems to
solve the original problem. reverse("abcd")
should return "dcba" - Write the following method public static int
toInt (String s) recursively. It is like
parseInt, but only for non-negative
valuestoInt("138") should return 138
27Problems (continued)
- Write the following method public static
boolean equals (String s1, String s2)
recursively. Its recursive structure is like that
of the equals methods for ints. - Write the following method public static int
compare (String s1, String s2) recursively.
Its recursive structure is similar to equals,
above.