Slides on Recursion

advertisement
Recursion
What is recursion?
In the context of a programming language - it
is simply an already active method (or
subprogram) being invoked by itself directly
or being invoked by another method (or
subprogram) indirectly.
Types of Recursion
Direct Recursion:
procedure Alpha;
begin
Alpha
end;
Indirect (or Mutual)
Recursion:
procedure Alpha;
begin
Beta
end;
procedure Beta;
begin
Alpha
end;
Difficult in some
programming languages
because of forward
reference.
Illustration
Suppose we wish to formulate a list of instructions to
explain to someone how to climb to the top of a ladder.
Consider the following pseudo-code statements Iterative statements:
procedure Climb_Ladder;
begin
for (i = 0; i < numRungs; i++)
move up one rung;
end;
Recursive statements:
procedure Climb_Ladder;
begin
if (at top) //the escape
mechanism
then stop;
else begin
move up one rung;
Climb_Ladder;
end
end;
Why use recursion?
There is a common belief that it is easier to learn
to program iteratively, or to use nonrecursive
methods, than it is to learn to program recursively.
Some programmers report that they “would be
fired” if they were to use recursion in their jobs.
In fact, though, recursion is a method-based
technique for implementing iteration.
Hard to argue conclusively for
recursion!
However, recursive programming is easy
once one has had the opportunity to practice
the style.
Recursive programs are often more
succinct, elegant, and easier to understand
than their iterative counterparts.
Some problems are more easily solved
using recursive methods than iterative ones.
Example: Fibonacci Numbers
F0
F1
F2
F3
F4
F5
F6
F7
…
0
1
1
2
3
5
8
13
…
Fibonacci numbers have an incredible number of properties that crop
up in computer science.
• There is a journal, The Fibonacci Quarterly, that exists for the sole
purpose of publishing theorems involving Fibonacci numbers.
Examples:
•
1. The sum of the squares of two consecutive Fibonacci numbers is another Fibonacci
number.
2. The sum of the first “n” Fibonacci numbers is one less than Fn+2
• Because the Fibonacci numbers are recursively defined, writing a
recursive procedure to calculate Fn seems reasonable.
• Also, from my earlier argument, we should expect the algorithm to be
much more elegant and concise than an equivalent iterative one.
• Consider an iterative solution!
Non-recursive Fibonacci Numbers
private static int fibonacci(int n)
{
int Fnm1, Fnm2, Fn;
if (n <=1)
else
}
return n //F0 = 0 and F1 = 1;
{
}
Fnm2 = 0;
Fnm1 = 1;
for (int i = 2; i <= n; i++)
{
Fn := Fnm1 + Fnm2;
Fnm2 := Fnm1;
Fnm1 := Fn;
}
return Fn
Not very elegant!!
Note:
F0 = 0
F1 = 1
F2 = 1
F3 = 2
F4 = 3
.
.
.
Fibonacci Numbers
Recursive Definition:
Fib(n) =
ì
ï
ï
ï
ï
ï
í
ï
ï
ï
ï
ïî
0,
if
n =0
1,
if
n =1
Fib(n-1)+Fib(n-2),
otherwise
Series: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...
Java:
int fib(int n)
{
if (n == 0)
return 0;
else if (n == 1)
return 1;
else return( fib(n-1) + fib(n-2));
}
The underlying problem with the recursive method is that it
performs lots of redundant calculations.
•
Example: on a reasonably fast computer, to recursively compute F40 takes almost a minute. A lot of time considering
that the calculation requires only 39 additions.
•
Example: to compute F5
F5
+
F4
F3
F2
F1
+
+
F2
F1
F1
+
F3
+
F2
F0 F1
+
F0
F1
F0
REDUNDANT!!
It turns out that the number of recursive calls is larger than the Fibonacci number we’re trying to calculate - and it has
an exponential growth rate.
•
Example: n=40, F40 = 102,334,155
The total number of recursive calls is greater than - 300,000,000
History of Fibonacci Numbers
The origin of the Fibonacci numbers and their sequence can be found in a
thought experiment posed in 1202. It was related to the population growth
of pairs of rabbits under ideal conditions. The objective was to calculate
the size of a population of rabbits during each month according to the
following rules (the original experiment was designed to calculate the
population after a year):
1. Originally, a newly-born pair of rabbits, one male and one female, are
put in a field.
2. Rabbits take one month to mature.
3. It takes one month for mature rabbits to produce another pair of newlyborn rabbits.
4. Rabbits never die.
5. The female always produces one male and one female rabbit every
month from the second month on.
Rabbit Genealogical Tree
Solving Problems
We encourage students to solve “large”
problems by breaking their solutions up into
“smaller” problems
These “smaller” problems can then be
solved and combined (integrated) together
to solve the “larger” problem.
Referred to as: Stepwise Refinement,
Decomposition, Divide-and-conquer
Basis of Recursion
If the subproblems are similar to the original
-- then we may be able to employ recursion.
Two requirements:
(1)
(2)
the subproblems must be simpler than
the original problem.
After a finite number of subdivisions, a
subproblem must be encountered that
can be solved outright.
How is memory managed?
Consider the following
Java program:
public class MemoryDemo
{
int I, J, K;
public static void main(String[] args)
{
. . .
three();
. . .
}
private static void one
{
float X, Y, Z;
}
. . .
return;
private static void two
{
char B, C, D;
}
}
. . .
one()
. . .
private static void three;
{
boolean P, Q, R;
. . .
two();
. . .
}
Say that the main program
MemoryDemo has invoked method
three, three has invoked two, and two
has invoked one.
Recall that parameters and local
variables are allocated memory upon
entry to a method and memory is
deallocated upon exit from the method .
Thus, the memory stack would
currently look like:
Activation Records:
---Available space --X
Y
Z
One
B
C
D
Two
P
Q
R
Three
I
J
K
MemoryDemo
Is memory managed differently
for recursion?
No!!
Consider the following
Java program:
public class MemoryDemo
{
int I, J, K;
public static void main(String[] args)
{
. . .
recursiveOne();
. . .
}
}
private static void recursiveOne();
{
float X,Y,Z;
. . .
recursiveOne();
. . .
}
Parameters and local variables are still
allocated memory upon entry to a
method and deallocated upon exit from
the method.
Thus, during the recursion, the memory
stack would look like:
. . .
. . .
X
Y
Z
RecursiveOne 3
X
Y
Z
RecursiveOne 2
X
Y
Z
RecursiveOne 1
I
J
K
MemoryDemo
Observation!
Some “escape mechanism” must be present
in the recursive method (or subprogram) in
order to avoid an infinite loop.
Iterative methods (with infinite loops) are
terminated for exceeding time limits.
Recursive methods (with infinite loops) are
terminated due to memory consumption.
Iteration
Entry
Initialization
Decision Done
Not Done
Update
Computation
Return
Recursion
Entry
Prologue
(SAVE state of
calling program
Save formal parameters,
local variables, return address
Test
Intermediate Level
(continue)
Partial computation
Stop recursion
Body
Procedure call
To itself
Final Computation
Restore (most recently
Saved) formal parameters,
Local variables, return address
Exit
To return address
Epilogue
(restore SAVE
state)
Consider an Example
(Summing a series of integers from 1 to n)
Iteratively public class SumDemo
{
public static void main(String args[])
{
int sum;
int n = 10; // could have user input
sum = iterativeSum(n);
System.out.println("Sum = " + sum);
}
private static int iterativeSum(int n)
{
int tempSum = n;
while ( n > 1)
{
n--;
tempSum += n; //tempSum = tempSum + n
}
return (tempSum);
}
}
Recursively public class SumDemo
{
public static void main(String args[])
{
int sum;
int n = 10; //could have user input this
sum = recursiveSum(n);
System.out.println("Sum = " + sum);
}
private static int recursiveSum(int n)
{
if (n <= 1)
return n;
else return ( n + recursiveSum(n-1));
}
}
Formal Representation Methods
Used by computer scientists for producing a
mathematically rigorous representation of a set of user
requirements.
Two classes of Representation Methods
State oriented notations:
Examples (Decision Tables, Finite-state Machines)
Relational notations:
Examples (Recurrence Relations, Algebraic
Axioms)
Using representation notations
Recall the earlier example of “summing the integers from 1 to n”
This can be described as a series using the
following representation:
1
Sum = n + (n-1) + (n-2) + ... + 1 or å i
i=n
Alternatively, recursive definitions
(recurrence relations) can be employed:
ì
n, if n £1
ï
Sum(n) = íï
în + Sum(n -1),
otherwise
Consider another example Computing Factorials
Definitions:
Iterative –
n! =
ì
ï
ï
ï
ï
ï
í
ï
ï
ï
ï
ï
î
1, if
n=0
1*2*3*...*n, if
n 0
Recursive –
ìï
1, if n = 0
n! = í
ïî(n -1)!*n, otherwise
Java:
public int factorial(int n)
{
if (n == 0)
return 1;
else return ( n * factorial(n-1));
}
Computing a power (xn)
Recall that standard Java does not provide an exponentiation operator:
the method pow in the Math class must be used (e.g., Math.pow(2,3))
Calculating powers can also be done using the relationship:
xn = exp(n * ln(x))
ì
1, if n = 0
Could also be done recursively:
ï
Recursive Definition: power(x,n)= í
x, if n = 1
ï
î x * power(x, n -1), otherwise
Java:
public float power(double x, int n
{
if (n == 0)
return 1.0;
else if (n == 1)
return x;
else return ( x * power(x,n-1));
}
Summing the elements of an array
named List that contains n values.
**Assuming lower bound of 0 as for Java
Recursive definition:
list[0], if n=1
Sum(list,n) =
list[n-1]+Sum(list,n-1), otherwise
Java:
ì
ïï
í
ï
ïî
public int sumArray(int[] list, int n)
{
// list is the array to be summed; n represents the number
//of elements currently in the array
if (n == 1)
return list[0];
else return (list[n-1] + sumArray(list,n-1));
}
//note: non-primitive parameters are passed by reference in java - the array is not
duplicated. Might be in other languages.
Using Scope!
Java:
public int sumArray(int n)
{
// access the array (in this case list) to be
// summed through scope; n represents the number of
// elements currently in the array
if (n == 1)
return list[0];
else return (list[n-1] + sumArray(n-1));
}
//in languages where parameters are duplicated – access
// arrays through scope
Sequentially search the elements of an array
for a given value(known to be in the array)
Recursive Definition Search(List, i, Value)
ì
i, if List[i] = Value
=í
îSearch(List,i +1,Value), otherwise
Note: (1) Assumes that the desired Value is in the array,
(2) Initial call is: positionOfValue = Search(list,0,Value)
(3) Returns the array position if Value is found in the array
Java:
public int search(int[] List, int i, int Value)
{
if (List[i] == Value)
return i;
else return Search(List,i+1,Value);
}
Sequentially search the elements of an array
for a given value(may not in the array)
Recursive Definition Search(List,i,value,numEls) =
ì
ï
ï
ï
í
ï
ï
ï
î
i, if List[i]==value
-99, if i==numEls
Search(List,i+1,Value,numEls), otherwise
Note: (1) Assumes that the desired value may not be in the array, returns -99 is not
(2) Initial call is: positionOfValue = Search(list,0,value,numEls)
(3) numEls is the number or elements in the array (its size)
Java:
public int search(int[] List, int i, int value, int numEls)
{
if (List[i] == value)
return i;
else if (i == numEls)
return -99;
else return Search(List,i+1,value,numEls);
}
Other Examples:
Reversing a character string passed as parameter s
public void reverse (String s)
{
char t = s.charAt(0);
if (s.length() > 1)
reverse(s.substring(1,s.length()));
System.out.print(t);
}
Converting an integer value to binary private static void convertToBinary(int x);
{
int t = x/2; // integer divide, truncate remainder
if (t != 0)
convertToBinary (t);
System.out.print(x % 2);
}
Another Example:
A boolean method that checks to see if two arrays passed as
parameters are identical in size and content (n is the size of the array).
areIdentical(x,y,n)
ì false, if (x.length != y.length)
// different sizes
ïï(x[0] == y[0]), if (n == 1) // t | f only one element
=í
x[n - 1]!= y[n - 1]) // f , if elements !=
ï false, if (
ïî areIdentical(x, y,n -1), otherwise
// continue
public boolean areIdentical(int[] x, int[] y, int n)
{
if (x.length != y.length)
return false;
else if (n == 1)
return (x[0] == y[0]);
else if (x[n-1] != y[n-1])
return false;
else return areIdentical(x,y,n-1);
}
Palindromes
Consider the following – assume no mixed case
boolean is_palindrome(String s)
{
// separate case for shortest strings
if (s.length() <= 1)
return true;
// get first and last character
char first = s.charAt(0);
char last = s.charAt(s.length()-1);
if (first == last)
{
//substring(1,s.length(0-1) returns a string
//between 1st and length-1 (not inclusive)
String shorter = s.substring(1, s.length()-1);
return is_palindrome(shorter);
}
else
return false;
}
Still Another Example:
What does the following java method do?
public int whoKnows(int[] x, int n)
{
if (n == 1)
return x[0];
else
return Math.min(x[n-1], whoKnows(x, n-1));
}
Traversing a Sequential Linked List
Iteratively: method invoked by - traverseList(p)
Algorithm traverseList(Node t)
Node temp = t;
while (temp != null)
{
visit (temp.data());
temp = temp.next(); //move to next node
}
return
Recursively: method invoked by - traverseList(p)
Algorithm traverseList(Node t)
if (t != null)
{
visit (t.data());
traverseList(t.next);
}
return
Reversing Nodes in a “null-terminated” Sequential Linked List
Iteratively: method invoked by - p = reverseList(p)
Algorithm reverseList(Node t) returns Node
Node p = t;
Node q = null;
while (p != null)
{
r = q;
q = p;
p = p.next;
q.next = r;
}
return q
Recursively:
method invoked by - p = reverseList(p,null)
Algorithm reverseList(Node x, Node y) returns Node
Node t;
if (x == null)
then return y;
else {
t = reverseList(x.next, x);
x.next = y;
return t;
}
More Linked List Examples
Algorithm to copy a linked list
public Node copyList (Node p)
{
Node q;
q = null;
if (p != null)
{
q = new Node();
q.data = p.data;
q.link = copyList(p.link);
}
return q;
}
Are two linked lists identical?
public boolean
identicalLists (Node s, node t)
{
boolean x = false;
if ((s == null) && (t == null))
return (true);
else if ((s != null) && (t != null))
{
if (s.data == t.data)
x = true;
else x = false;
if (x)
return (identicalLists(s.link, t.link);
}
}
Linked List Examples
Algorithm to count the number of nodes in a linked list
public int countNodes (Node s)
{
if (s != null)
return ( 1 + countNodes(s.next);
else
return 0;
}
Other applications:
Traversing non-linear data structures (trees)
Sorting (Quicksort, etc.)
Searching (Binary search)
Find all URLs reachable, directly or indirectly, for a given Web Page
Mutual Recursion
Definition: a form of recursion where two mathematical or computational
objects are defined in terms of each other. Very common in some problem domains,
such as recursive descent parsers.
Example:
function1()
{
//do something
f2();
//do something
}
function2()
{
//do something
f1();
//do something
}
Mutual Recursion
A standard example of mutual recursion (admittedly artificial) is
determining whether a non-negative integer is even or is odd. Uses two
separate functions and calling each other, decrementing each time.
Example:
boolean even( int number )
{
if( number == 0 )
return true;
else
return odd(Math.abs(number)1);
}
boolean odd( int number )
{
if( number == 0 )
return false;
else
return even(Math.abs(number)1);
}
Mutual Recursion
A contrived, and not efficient at all, example of calculating
Fibonacci numbers using mutual recursion
Example:
int Babies(int n)
{
if(n==1)
return 1;
else
return Adults(n-1);
}
int Adults(int n)
{
if(n==1)
return 0;
else
return Adults(n-1) + Babies(n-1);
}
int Fib_Mutual_Rec(int n)
{
return Adults(n) + Babies(n);
// return Adults(n+1); // is also valid
// return Babies(n+2); // is also valid
}
Primitive Recursion vs Non-primitive
Recursion
All examples that we have seen to this point
have used primitive recursion.
Non- primitive recursion:
Ackerman(m,n) =
ì
n + 1,
if
ï
í
Ack(m - 1,1),
if
ï
î Ack(m - 1, Ack(m, n - 1)), if
m=0
m ¹ 0 and n = 0
m ¹ 0 and n ¹ 0
Pitfalls of Recursion
 Missing base case – failure to provide an escape case.
 No guarantee of convergence – failure to include within a recursive
function a recursive call to solve a subproblem that is not smaller.
 Excessive space requirements - a function calls itself recursively an
excessive number of times before returning; the space required for
the task may be prohibitive.
 Excessive recomputation – illustrated in the recursive Figonacci
method which ignors that several sub-Fibonacci values have already
been computed.
Disadvantages of Recursion
Method calls may be time-consuming.
Recursive methods may take longer to run.
More dynamic memory is used to support
recursion.
Download