Transcript Recursion

CSCI 3333 Data Structures
Introduction to Recursion
by
Dr. Bun Yue
Professor of Computer Science
[email protected]
http://sce.uhcl.edu/yue/
2013
Acknowledgement




Mr. Charles Moen
Dr. Wei Ding
Ms. Krishani Abeysekera
Dr. Michael Goodrich
Recursion


Recursion is a way to combat
complexity and solve problems.
Solving a problem by:


solving the problem for ‘trivial’ cases,
and
solving the same problem, but with
smaller sizes.
Recursive Definition
A recursive definition: a definition in
terms of itself.
Example:
 0 is a natural number.
 If x is a natural number, then x+1
is a natural number. (or x is a
natural number if x-1 is a natural
number).

Mathematical Induction



Proof by Induction is recursive in
nature.
To prove S(n) for all n = 1, 2, 3.
Show:
1.
2.
S(n) is true for n = 1.
If S(k) is true for all k from 1 to n-1,
then S(n) is true
Recursive Methods
A recursive method may call itself.
Example: factorial.
Iterative definition:
n! = n . (n-1) . (n-2) … 3 . 2 . 1

Recursive definition:
1
if n  0

f ( n)  
else
n  f (n  1)
Factorial Implementation: Fac-1

Found here in C++:
int factorial(int number)
{ int temp;
if (number <= 1) return 1;
temp = number * factorial(number - 1);
return temp;
}

What do you think?
Another Implementation: Fac-2
// recursive factorial function in Java:
public static int recursiveFactorial(int n)
{
if (n == 0) return 1;
// base case
else return n *
recursiveFactorial(n- 1);
// recursive case
} // By Goodrich

What do you think?
Content of a Recursive Method

Base case(s): exit conditions



Values of the input variables (exit
conditions) for which we perform no
recursive calls are called base cases
(there should be at least one base case).
Every possible chain of recursive calls
must eventually reach a base case.
Recursive calls: recursive conditions


Calls to the current method.
Each recursive call should be defined so
that it makes progress towards a base
case.
Visualizing Recursion



Example recursion trace:
Recursion trace
return 4*6 = 24
final answer
A box for each
recursiveFactorial (4)
recursive call
return 3*2 = 6
An arrow from each
recursiveFactorial (3)
caller to callee
return 2*1 = 2
An arrow from each
recursiveFactorial (2)
callee to caller
return 1*1 = 1
showing return value
recursiveFactorial (1)
call
call
call

call
call
recursiveFactorial (0)
return 1
Tips on Recursive Analysis


Identify exit and recursive
conditions.
Ensure that the conditions cover all
input ranges.
Input Range Analysis of Fac


Fac-1 and Fac-2: n (or number) has
the type of int.
Fac-2 may be caught in circularity
forever for negative numbers.

This is especially serious in Java as it
does not support unsigned int.
Complexity Analysis
To analyze the time complexity of a
recursive routine, a recurrence
relation may need to be identified
and solved.
 E.g. for factorial:
T(0) = d
T(N) = T(N-1) + c
Solving by substitution: T(N) = cN +
d = O(N)

Reversing an Array
Algorithm ReverseArray(A, i, j):
Input: An array A and nonnegative
integer indices i and j
Output: The reversal of the elements in
A starting at index i and ending at j
if i < j then
Swap A[i] and A[ j]
ReverseArray(A, i + 1, j - 1)
end if;
return;
Tips on Constructing Recursion



In creating recursive methods, it is
important to define the methods in
ways that facilitate recursion.
This sometimes requires that we
may define additional parameters
that are passed to the method.
For example, we defined the array
reversal method as ReverseArray(A,
i, j), not ReverseArray(A).
Implementation in Java
public static void arrayReversal (int[] a) {
arrayReversalWithRange(a, 0, a.length-1);
}
private static void arrayReversalWithRange
(int[] a, int i, int j) {
if (i < j)
{
int temp = a[i];
a[i] = a[j];
a[j] = temp;
arrayReversalWithRange(a,i+1,j-1);
}
}
Calling arrayReversal
public static void main (String args[]) {
int b[] = {1,2,3,4,5,6,7};
arrayReversal(b);
for (int i=0; i<b.length; i++)
{ System.out.println(b[i] + " ");
}
}
Computing Powers

The power function, p(x,n)=xn, can be
defined recursively:
1
if n  0

p ( x, n )  
else
 x  p( x, n  1)


This leads to an power function that runs
in linear time (for we make n recursive
calls).
We can do better than this, however.
Recursive Squaring

We can derive a more efficient linearly
recursive algorithm by using repeated
squaring:
1
if x  0


p( x, n)   x  p( x, (n  1) / 2) 2 if x  0 is odd
 p( x, n / 2) 2
if x  0 is even


For example,
24 = 2(4/2)2 = (24/2)2 = (22)2 = 42 = 16
25 = 21+(4/2)2 = 2(24/2)2 = 2(22)2 = 2(42) = 32
26 = 2(6/ 2)2 = (26/2)2 = (23)2 = 82 = 64
27 = 21+(6/2)2 = 2(26/2)2 = 2(23)2 = 2(82) = 128.
A Recursive Squaring Method
Algorithm Power(x, n):
Input: A number x and integer n
Output: The value xn
if n = 0 then
return 1
if n is odd then
y = Power(x, (n - 1)/ 2)
return x · y · y
else
y = Power(x, n/ 2)
return y · y
Analyzing the Recursive Squaring
Method
Algorithm Power(x, n):
Input: A number x and integer
Output: The value xn
if n = 0 then
return 1
if n is odd then
y = Power(x, (n - 1)/ 2)
return x · y · y
else
y = Power(x, n/2)
return y · y
n
Each time we make a
recursive call we halve the
value of n; hence, we make
log n recursive calls. That
is, this method runs in
O(log n) time.
It is important that we
used a variable twice here
rather than calling the
method twice.
Further Analysis


Need to limit the range of n, or.
Add the recursive condition:
if n < 0 return 1 / Power(x, -n);
Tail Recursion 1



Tail recursion occurs when a linearly
recursive method makes its recursive
call as its last step.
The array reversal method is an
example.
Such methods can easily be converted
to non-recursive methods (which
saves on some resources), often by
the compiler and runtime system.
Tail Recursion Example
Algorithm IterativeReverseArray(A, i, j ):
Input: An array A and nonnegative integer
indices i and j
Output: The reversal of the elements in A
starting at index i and ending at j
while i < j do
Swap A[i ] and A[ j ]
i =i+1
j =j-1
return
Tail Recursion 2


Tips: for linear recursion, write your
method using tail recursion.
Example: Fac-1 does not use tail
recursion. Fac-2 does.
Binary Recursion


Binary recursion occurs whenever there are
two recursive calls for each non-base case.
Example: the DrawTicks method for drawing
ticks on an English ruler.
A Binary Recursive Method for Drawing
Ticks
// draw a tick with no label
public static void drawOneTick(int tickLength) { drawOneTick(tickLength, - 1); }
// draw one tick
public static void drawOneTick(int tickLength, int tickLabel) {
for (int i = 0; i < tickLength; i++)
System.out.print("-");
if (tickLabel >= 0) System.out.print(" " + tickLabel);
System.out.print("\n");
}
public static void drawTicks(int tickLength) { // draw ticks of given length
if (tickLength > 0) {
// stop when length drops to 0
drawTicks(tickLength- 1);
// recursively draw left ticks
drawOneTick(tickLength);
// draw center tick
drawTicks(tickLength- 1);
// recursively draw right ticks
}
}
public static void drawRuler(int nInches, int majorLength) { // draw ruler
drawOneTick(majorLength, 0);
// draw tick 0 and its label
for (int i = 1; i <= nInches; i++)
{
drawTicks(majorLength- 1);
// draw ticks for this inch
drawOneTick(majorLength, i);
// draw tick i and its label
}
}
Note the two
recursive calls
Binary Recursion


Binary Recursions are expensive.
If possible, convert it to linear
recursion.
Fibonacci Numbers

Fibonacci numbers are defined
recursively:
F0 = 0
F1 = 1
Fi = Fi-1 + Fi-2
for i > 1.
Recursive Fibonacci Algorithm
Algorithm BinaryFib(k):
Input: Nonnegative integer k
Output: The kth Fibonacci number Fk
if k = 1 then
return k
else
return BinaryFib(k - 1) + BinaryFib(k - 2)


Binary recursion from Goodrich’s book.
Any problem here?
A C++ Implementation
long fib(unsigned long n) {
if (n <= 1) {
return n;
} else {
return fib(n-1)+fib(n-2);
}
}
Fibonacci Number

Negative Fibonacci Number:




F-1 = 1,
F-2 = -1,
Fn = F(n+2)−F(n+1).
1,-1,2,-3,5,-8,13,…
Analyzing the Binary Recursion
Fibonacci Algorithm

Let nk denote number of recursive calls made by
BinaryFib(k). Then










n0
n1
n2
n3
n4
n5
n6
n7
n8
=
=
=
=
=
=
=
=
=
1
1
n1
n2
n3
n4
n5
n6
n7
+
+
+
+
+
+
+
n0
n1
n2
n3
n4
n5
n6
+
+
+
+
+
+
+
1
1
1
1
1
1
1
=
=
=
=
=
=
=
1+1+1=3
3+1+1=5
5+3+1=9
9 + 5 + 1 = 15
15 + 9 + 1 = 25
25 + 15 + 1 = 41
41 + 25 + 1 = 67.
Note that the value at least doubles for every other
value of nk. That is, nk > 2k/2. It is exponential!
A Better Fibonacci Algorithm

Use linear recursion instead:
Algorithm LinearFibonacci(k):
Input: A nonnegative integer k
Output: Pair of Fibonacci numbers (Fk, Fk-1)
if k = 1 then
return (k, 0)
else
(i, j) = LinearFibonacci(k - 1)
return (i +j, i)

Runs in O(k) time.
Implementation


Java and C++ do not return multiple
values.
Perl’s implementation:
sub fib {
return (1,0) if $_[0] == 1;
my ($i, $j) = fib($_[0]-1);
return ($i+$j, $i);
}
print "fib(13): " + fib(13) + "\n";
Java implementation
public static int Fab(int n) {
if (n <= 2) return 1;
return FabHelper(n,2,1,1);
}
private static int FabHelper(int n, int k, int fk,
int fk_1) {
if (n == k) return fk;
else
return FabHelper(n, k+1, fk + fk_1, fk);
}
Questions and Comments?