You are on page 1of 54

RECURSIVE

DIFFERENT VIEWS OF RECURSION


Recursive Definition: n! = n * (n-1)!
(non-math examples are common too)
Recursive Procedure: a procedure that calls
itself.
Recursive Data Structure: a data structure
that contains a pointer to an instance of
itself:
public class ListNode {
Object nodeItem;
ListNode next, previous;

}
2

RECURSION IN ALGORITHMS

Recursion is a technique that is useful

for defining relationships, and


for designing algorithms that implement those relationships.

Recursion is a natural way to express many algorithms.


For recursive data-structures, recursive algorithms are a
natural choice

WHAT IS RECURSION?
A Definition Is Recursive If It Is Defined In
Terms Of Itself

We use them in grammar school e.g. what is a noun


phrase?

a noun

an adjective followed by a noun phrase

Descendants

the persons children

all the childrens descendants

WHAT IS RECURSION?
Think

self-referential definition

definition is recursive if it is defined in terms


of itself

Exponentiation - x raised to the y power

if y is 0, then 1

otherwise
its x * (x raised to the y-1 power)

OTHER RECURSIVE DEFINITIONS IN


MATHEMATICS
Factorial:
n! = n (n-1)! and 0! = 1! = 1
Fibonacci numbers:
F(0) = F(1) = 1
F(n) = F(n-1) + F(n-2) for n > 1
Note base case

Definition cant be completely self-referential


Must eventually come down to something thats solved
directly

I KNOW THE STEPS NEEDED TO WRITE A SIMPLE


RECURSIVE METHOD IN JAVA

St
ro
ng

ly

Di
s

is
a

ag
re
e

gr
ee

gr
ee

15%

4.

28%

25%

Ag
re
e

3.

33%

ly

2.

Strongly Agree
Agree
Disagree
Strongly Disagree

St
ro
ng

1.

RECURSIVE FACTORIAL

public static int factorial (int n) {


if (n == 1)
return 1;
else
return n * factorial(n-1);
}

Exercise: trace execution (show method calls) for


n=5

WHY DO RECURSIVE METHODS WORK?


Activation Records on the Run-time Stack are the
key:
Each time you call a function (any function) you get a
new activation record.
Each activation record contains a copy of all local
variables and parameters for that invocation.
The activation record remains on the stack until the
function returns, then it is destroyed.
Try yourself: use your IDEs debugger and put a
breakpoint in the recursive algorithm
Look at the call-stack.

BROKEN RECURSIVE FACTORIAL

public static int Brokenfactorial(int n){


int x = Brokenfactorial(n-1);
if (n == 1)
return 1;
else
return n * x;
}
Whats

wrong here? Trace calls by hand

BrFact(2) -> BrFact(1) -> BrFact(0) -> BrFact(-1) ->


BrFact(-2) ->
Problem: we do the recursive call first before
checking for the base case
Never stops! Like an infinite loop!
10

RECURSIVE DESIGN
Recursive methods/functions require:
1) One or more (non-recursive) base cases
that will cause the recursion to end.
if (n == 1) return 1;
2) One or more recursive cases that operate
on smaller problems and get you closer to the
base case.
return n * factorial(n-1);
Note: The base case(s) should always be
checked before the recursive call.
11

RULES FOR RECURSIVE ALGORITHMS

Base case - must have a way to end the recursion

Recursive call - must change at least one of the


parameters and make progress towards the base
case

exponentiation (x,y)

base: if y is 0 then return 1

recursive: else return (multiply x times exponentiation(x,y-1))

12

DESIGNING ALGORITHMS

There is no single recipe for inventing algorithms

There are basic rules:


Understand your problem well may require much mathematical
analysis!
Use existing algorithms (reduction) or algorithmic ideas

There is a single basic algorithmic technique:

Divide and Conquer

In its simplest (and most useful) form it is simple induction


In order to solve a problem, solve a similar problem of smaller
size

The key conceptual idea:


Think only about how to use the smaller solution to get the larger
one
Do not worry about how to solve to smaller problem (it will be
solved using an even smaller one)

RECURSION
A recursive method is a method that contains a call
to itself
Technically:

All modern computing languages allow writing methods


that call themselves
We will discuss how this is implemented later

Conceptually:
This allows programming in a style that reflects divide-nconquer algorithmic thinking
At the beginning recursive programs are confusing
after a while they become clearer than non-recursive
variants

FACTORIAL
public static void Factorial {
public static void main() {
System.out.println(5!= + factorial(5));
}
public static long factorial(int n){
if (n == 0)
return 1;
else
return n * factorial(n-1);
}

ELEMENTS OF A RECURSIVE PROGRAM

Basis: a case that can be answered without using


further recursive calls

if (n==0) return 1;

Creating the smaller problem, and invoking a


recursive call on it

In our case:

In our case: factorial(n-1)

Finishing to solve the original problem

In our case: return n * /*solution of recursive call*/

TRACING THE FACTORIAL METHOD


System.out.println(5!= + factorial(5))
5 * factorial(4)
4 * factorial(3)
3 * factorial(2)
2 * factorial(1)
1 * factorial(0)
return 1
return 1
return 2
return 6
return 24
return 120

CORRECTNESS OF FACTORIAL METHOD


Theorem: For every positive integer n,
factorial(n) returns the value n!.
Proof: By induction on n:
Basis: for n=0, factorial(0) returns 1=0!.
Induction step: When called on n>1, factorial calls
factorial(n-1), which by the induction
hypothesis returns (n-1)!. The returned value is
thus n*(n-1)!=n!.

RAISING TO POWER TAKE 1


public static double power(double x, long n) {
if (n == 0) return 1.0;
return x * power(x, n-1);
}

RUNNING TIME ANALYSIS


Simplest way to calculate the running time of a
recursive program is to add up the running times of
the separate levels of recursion.
In the case of the power method:

There are n+1 levels of recursion

power(x,n), power(x,n-1), power(x, n-2), power(x,0)

Each level takes O(1) steps


Total time = O(n)

RAISING TO POWER TAKE 2


public static double power(double x, long n) {
if (n == 0) return 1.0;
if (n%2 == 0) {
double t = power(x, n/2);
return t*t;
}
return x * power(x, n-1);
}

ANALYSIS
n
x
Theorem: For any x and positive integer n, the power

method returns .
Proof: by complete induction on n.
Basis: For n=0, we return 1.
If n is even, we return power(x,n/2)*power(x,n/2).
By then / 2 2
n/2
x ) xn
induction hypothesis power(x,n/2) x
returns
, so we (return
.
If n is odd, we return x*power(x,n-1).
By the induction
n 1
n
n 1
x

x
x
hypothesis power(x,n-1) returns
, so we return
.

The running time is now O(log n):


After 2 levels of recursion n has decreased by a factor of at
least two (since either n or n-1 is even, in which case the
recursive call is with n/2)
Thus we reach n==0 after at most 2log2n levels of recursion
Each level still takes O(1) time.

public
class Reverse {
EVERSE
static InputRequestor in = new InputRequestor():
public static void main(String[] args) {
printReverse();
}
public static void printReverse() {
int j = in.requestInt(Enter another Number+
(0 for end of list):);
if (j!=0){
printReverse();
System.out.println(j);
}
}
}

RECURSIVE DEFINITIONS
Many things are defined recursively.
Fibonaci Numbers: 1, 1, 2, 3, 5, 8, 13, 21,

fn = fn-1 + fn-2

Arithmetic Expressions. E.g. 2+3*(5+(3-4))


A number is an expression
For any expression E: (E) is an expression
For any two expressions E1, E2: E1+E2, E1-E2, E1*E2,
E1/E2 are expressions

Fractals

In such cases recursive algorithms are very natural

RECURSIVE FIBONACCI METHOD

This is elegant code, no?

long fib(int n) {
if ( n == 0 ) return 1;
if ( n == 1 ) return 1;
return fib(n-1) + fib(n-2);
}
Lets start to trace it for fib(6)

25

TRACE OF FIB(5)

For fib(5), we call fib(4) and fib(3)

For fib(4), we call fib(3) and fib(2)


For fib(3), we call fib(2) and fib(1)
For fib(2), we call fib(1) and fib(0). Base cases!
fib(1). Base case!
For fib(2), we call fib(1) and fib(0). Base cases!

For fib(3), we call fib(2) and fib(1)


For fib(2), we call fib(1) and fib(0). Base cases!
fib(1). Base case!

26

FIBONACCI: RECURSION IS A BAD CHOICE

Note that subproblems (like fib(2) ) repeat, and solved


again and again
We dont remember that weve solved one of our
subproblems before
For this problem, better to store partial solutions instead of
recalculating values repeatedly
Turns out to have exponential time-complexity!

27

NON-RECURSIVE FIBONACCI

Two bottom-up iterative solutions:

Create an array of size n, and fill with values starting


from 1 and going up to n
Or, have a loop from small values going up, but
only remember two previous Fibonacci values
use them to compute the next one
(See next slide)

28

ITERATIVE FIBONACCI
long fib(int n) {
if ( n < 2 ) return 1;
long answer;
long prevFib=1, prev2Fib=1; // fib(0) & fib(1)
for (int k = 2; k <= n; ++k) {
answer = prevFib + prev2Fib;
prev2Fib = prevFib;
prevFib = answer;
}
return answer;
}
29

NEXT: PUTTING RECURSION TO WORK

Divide and Conquer design strategy

Its form
Examples:
Binary Search
Merge Sort

Time complexity issues

30

FIBONACI NUMBERS
public class Fibonaci {
public static void main(String[] args)
for(int j = 1 ; j<20 ; j++)
System.out.println(fib(j));
}
public static int fib (int n) {
if (n <= 1) return 1;
return fib(n-1) + fib(n-2);
}
}

TURTLEFRACTAL
public class TurtleFractal {
static Turtle turtle = new Turtle();
public static void main(String[] args)
turtle.tailDown();
drawFractal(500,4);
}
public static void drawFractal(int
if (level==0)
turtle.moveForward(length);
else {
drawFractal(length/3, level-1)
turtle.turnLeft(60);
drawFractal(length/3, level-1)
turtle.turnRight(120);
drawFractal(length/3, level-1)
turtle.turnLeft(60);
drawFractal(length/3, level-1)
}
}}

length, int level){

;
;
;
;

FRACTALTURTLE OUTPUT

HOW TO THINK/DESIGN WITH RECURSION


Many people have a hard time writing recursive
algorithms
The key: focus only at the current stage of the
recursion

Handle the base case, then


Decide what recursive-calls need to be made

Assume they work (as if by magic)

Determine how to use these calls results

34

RECURSION VS. ITERATION


Interesting fact: Any recursive algorithm can be rewritten as an iterative algorithm (loops)
Not all programming languages support recursion:
COBOL, early FORTRAN.
Some programming languages rely on recursion
heavily: LISP, Prolog, Scheme.

35

TO RECURSE OR NOT TO RECURSE?


Recursive

solutions often seem elegant


Sometimes recursion is an efficient design
strategy
But sometimes its definitely not

Important! we can define recursively and


implement non-recursively
Many recursive algorithms can be re-written nonrecursively
Use an explicit stack
Remove tail-recursion (compilers often do this for you)

36

TO RECURSE OR NOT TO RECURSE?


Sorting

Selection sort vs. mergesort which to choose?

Factorial

Could just write a loop.


Any advantage to the recursive version?

Binary

search

We saw two versions. Which to choose?

Fibonacci

Lets consider Fibonacci carefully


37

DIVIDE AND CONQUER


It

is often easier to solve several small


instances of a problem than one large one.

divide the problem into smaller instances of the


same problem
solve (conquer) the smaller instances
recursively
combine the solutions to obtain the solution for
original input
Must be able to solve one or more small inputs
directly

This

is an algorithm design strategy

Computer scientists learn many more of these


38

GENERAL STRATEGY FOR DIV. & CONQ.


Solve (an input I)
n = size(I)
if (n <= smallsize)
solution = directlySolve(I);

else
divide I into I1, , Ik.
for each i in {1, , k}
Si = solve(Ii);
solution = combine(S1, , Sk);

return solution;

39

WHY DIVIDE AND CONQUER?


Sometimes

its the simplest approach


Divide and Conquer is often more efficient
than obvious approaches

E.g. BinarySearch vs. Sequential Search


E.g. Mergesort, Quicksort vs. SelectionSort

But,

not necessarily efficient

Might be the same or worse than another approach

We

must analyze each algorithm's time


complexity
Note: divide and conquer may or may not be
implemented recursively
40

TOP-DOWN STRATEGY
Divide

and Conquer algorithms illustrate a


top-down strategy

Given a large problem, identify and break into


smaller subproblems
Solve those, and combine results

Most

recursive algorithms work this way


The alternative? Bottom-up
Identify and solve smallest subproblems first
Combine to get larger solutions until solve entire
problem

41

BINARY SEARCH OF A SORTED ARRAY

first

mid

last

Strategy
Find the midpoint of the array
Is target equal to the item at midpoint?
If smaller, look in the first half
If larger, look in second half

42

BINARY SEARCH (NON-RECURSIVE)


int binSearch ( array[], target) {
int first = 0; int last = array.length; int result = -1;
while ( first <= last && result == -1 ) {
mid = (first + last) / 2;
if ( target == array[mid] ) return mid; // found it
else if ( target < array[mid] ) // must be in 1st half
last = mid -1;
else // must be in 2nd half
first = mid + 1
}
return result;
}
43

BINARY SEARCH (RECURSIVE)


int binSearch ( array[], first, last, target) {
if ( first <= last ) {
mid = (first + last) / 2;
if ( target == array[mid] ) // found it!
return mid;
else if ( target < array[mid] ) // must be in 1st half
return binSearch( array, first, mid-1, target);
else // must be in 2nd half
return binSearch(array, mid+1, last, target);
}}
No loop! Recursive calls take their place

But dont think about that if it confuses you!

Base cases checked first? (Why? Zero items? One item?)


44

MERGESORT IS CLASSIC DIVIDE & CONQUER

45

ALGORITHM: MERGESORT
Specification:

Input: Array E and indexes first, and Last, such that


the elements E[i] are defined for first <= i <= last.
Output: E[first], , E[last] is sorted rearrangement of
the same elements

Algorithm:

void mergeSort(Element[] E, int first, int last)


if (first < last)
int mid = (first+last)/2;
mergeSort(E, first, mid);
mergeSort(E, mid+1, last);
merge(E, first, mid, last); // merge two sorted halves

return;

46

EXERCISE: TRACE MERGESORT EXECUTION

Can you trace MergeSort() on this list?

A = {8, 3, 2, 9, 7, 1, 5, 4};

47

EFFICIENCY OF MERGESORT
Wait

for CS216 and CS432 to study


efficiency of this and other recursive
algorithms
But

It is more efficient that other sorts like


Selection Sort, Bubble Sort, Insertion Sort
Its O(n lg n) which is the same order-class as
the most efficient sorts (also quicksort and
heapsort)

The

point is that the D&C approach matters


here, and a recursive definition and
implementation is a win
48

MERGING SORTED SEQUENCES


Problem:

Given two sequences A and B sorted in nondecreasing order, merge them to create one sorted
sequence C
Input size: C has n items, and A and B have n/2

Strategy:

Determine the first item in C: it should be the


smaller of the first item in A and the first in B.
Suppose it is the first item of A. Copy that to C.
Then continue merging B with rest of A (without
the item copied to C). Repeat!
49

ALGORITHM: MERGE
merge(A, B, C)
if (A is empty)
append whats left in B to C

else if (B is empty)
append whats left in A to C

else if (first item in A <= first item in B)


append first item in A to C
merge (rest of A, B, C)

else // first item in B is smaller


append first item in B to C
merge (A, rest of B, C)

return

50

SUMMARY OF RECURSION CONCEPTS

Recursion is a natural way to solve many problems

Sometimes recursion produces an efficient solution


(e.g. mergesort)

Sometimes its a clever way to solve a problem that is


not clearly recursive

Sometimes it doesnt (e.g. fibonacci)

To use recursion or not is a design decision for your


toolbox

51

RECURSION: DESIGN AND


IMPLEMENTATION
The

Identify one or more base (simple) cases that


can be solved without recursion

Rules

In your code, handle these first!!!

Determine what recursive call(s) are needed for


which subproblems
Also, how to use results to solve the larger
problem
Hint: At this step, dont think about how recursive
calls process smaller inputs! Just assume they
work!

52

EXERCISE: FIND MAX AND MIN


Given

a list of elements, find both the maximum


element and the minimum element
Obvious solution:
Consider first element to be max
Consider first element to be min
Scan linearly from 2nd to last, and update if something
larger then max or if something smaller than min

Class

exercise:

Write a recursive function that solves this using divide


and conquer.
Prototype: void maxmin (list, first, last, max, min);
Base case(s)? Subproblems? How to combine results?

53

You might also like