Loading...

PPT – Chapter 8: Recursion PowerPoint presentation | free to view - id: fe2d-ZTE1N

The Adobe Flash plugin is needed to view this content

Chapter 8 Recursion

- Recursion is based on a problem solving principle

called divide and conquer - To solve the problem, break it down into a

smaller version of the same problem and solve the

smaller version - How do you solve the smaller version?
- Break down the smaller version into an even

smaller version and solve this new version of the

problem - How?
- Break this one into an even smaller problem
- This sounds too easy
- The trick is to identify how to break the problem

down and also how to solve the smallest version

of the problem

Simple Example Factorial

- Factorial is the value you get by multiplying an

integer by each smaller integer until you reach 1 - 5! 5 4 3 2 1
- We can solve this problem using iteration easily
- We can also solve this problem using recursion

easily by identifying the following - factorial(x) x factorial(x 1)
- we have identified a way to solve the problem by

applying a smaller version of the problem - if x is a positive integer, then x 1 is a

smaller positive integer, so we solve

factorial(x) by solving factorial(x 1)

int fact 1 for(int x n x 1 x--)

fact x

Implementing Recursion

- Recursion is implemented by simply calling the

same method within the method itself - For instance, if we have a factorial method as

defined as follows - public int factorial(int x)
- Then we write a recursive method which has code

that calls factorial with a smaller value for x

as in

return x factorial(x 1)

Infinite Recursion and a Base Case

- We have a slight problem with our implementation

of recursion and that is known as infinite

recursion - If we call factorial(4) then the code calls

factorial(3) which calls factorial(2) which calls

factorial(1) which calls factorial(0) which calls

factorial(-1) - When does it stop?
- Never
- We need to have a stopping point, a point where

factorial does not call itself, but returns a

value - The stopping point is called the base case (or

base cases) - What should our base case be for factorial?
- When the parameter is
- We add an if-else statement to make this work

The Recursive Factorial Method

public int factorial(int x) if(x return 1 else return x factorial(x-1)

Base case Recursive case

The iterative version public int factorial(int

x) int prod 1 for(n x n1

n--) prod n return prod

Will the two sets of code return the same

value? Try factorial(8), factorial(2), factorial(

1), factorial(0) and factorial(-5)

What Happens When a Method is Called?

- Before we examine why and how recursion works,

lets consider method calls in general - When a set of code calls a method, some

interesting things happen - A method call generates an activation record
- The activation record (AR) is placed on the

run-time stack - ARs will store the following information about

the method - Local variables of the method
- Parameters passed to the method
- Value returned to the calling code (if the method

is not a void type) - The location in the calling code of the

instruction to execute after returning from the

called method

The Run-time Stack

- Notice that we use a stack to accumulate

activation records. Why? - Calling methods is a LIFO activity
- Imagine that method1 calls method2 which calls

method3 - When method3 ends, where do we return to, method

1 or method2? - method2, which was the last one we were at,

therefore LIFO - Only when method2 ends do we return to method1
- Using a stack makes it easy to backtrack to the

proper location when a method ends

Run-time stack main calls m1 m1 calls m2 m2

calls m3 m3 calls m4 We are currently in m4

Main AR m1 AR m2 AR m3 AR m4 AR

stack pointer

Re-Examining Recursion

- Recursion works because of the run-time stack
- Each time a method is called, its AR is placed on

the top of the run-time stack - When that method ends, its AR is popped off the

run-time stack thus causing the system to return

to the previous code (whatever called it) - This allows us to backtrack
- The recursive method manipulates the data

pertaining to the problem, solving some portion

of it - So, the chain of method calls contribute to solve

the problem - As opposed to solving the problem through a

sequence of instructions that are executed

repeatedly by some form of loop

Recursion is Divide and Conquer

- In recursion, the same methods AR is placed onto

the run-time stack - But each time, the AR contains data from a

smaller problem or a subset of the initial data

(i.e. the parameters change) - Thus, this version of the method call is simpler

than the last - When the base case is reached, the method does

not call itself recursively, so it terminates the

recursion - Upon returning from the last method call, ARs are

popped off the run-time stack - This continues until we have reached the original

method call that started the recursion

Factorial Example

public int factorial(int x) if(x return 1 else return x factorial(x-1)

(point p2)

Imagine that we have factorial(4)

(point p1) This places an AR on the run-time

stack for factorial with x 4, no local

variables, and a return to the code

immediately after factorial(4) in the

original code When executing factorial with x

4, it calls factorial with x 3 pushing a new

AR on the run-time stack

Here is the run-time stack after factorial(3) is

called from factorial

Factorial Continued

factorial calls itself recursively with x-1

each time, so at first, we have factorial with x

4 and it calls factorial(3) In this version

of factorial, x 3 and it calls factorial(2)

In this version of factorial, x 2 and it calls

factorial(1) In this version of factorial, x

1 which is the base case. Rather than calling

factorial(x 1), it instead returns 1

stack pointer

Factorial Continued

Return 12

Once we have reached the base case, we begin to

return from the set of recursive calls The base

case returns the value 1 We return to the

instruction x factorial(x 1) where

factorial(x 1) 1 and x 2 causing this

method to return 2 1 2 returning to the

instruction x factorial(x 1) 3 2 so

this method returns 6 to x factorial(x

1) 4 3 so this method returns 12

Return 6

Return 2

Return 1

Thinking Recursively

- The key to thinking recursively is to see the

solution to the problem as a smaller version of

the same problem - This decomposition tells you exactly how to solve

it - except for the base case
- Then, identify the base case(s) and what the base

case(s) do - Your recursive method will then comprise an

if-else statement where the base case returns one

value and the non-base case recursively calls the

same method with a smaller parameter or set of

data - Note Forgetting the base case leads to infinite

recursion - Although in fact, your code wont run forever

like and infinite loop, instead, you will

eventually run out of stack space and get a

run-time error/exception called a stack overflow

Other Recursive Methods Power

Determine the value of xn

public double power(double x, int n)

if(x 0 n IllegalArgumentException(x is zero and n

n) else if (x 0) return

0 else if (n 0) return 1

else if (n 0) return x

power(x, n 1) else return 1 / power(x,

-n)

Base cases if x 0 and n error (00 is undefined) otherwise if x 0 then

the product is 0 (0n 0) otherwise if n 0

then the value is 1 (x0 1) Recursive cases

power(x, n) x power(x, n 1) for n 0 and

power(x, n) 1 / power(x, -n) for n

Other Recursive Methods Print Linked List

public void print(IntNode x) if(x !

null) System.out.println(x.getDat

a( )) print(x.getLink( ))

Base case if the pointer is null, then we

are at the end of the list Recursive case

otherwise, print out the data item of the

current node and recursively call the method

with the pointer to the next item in the

list printBackwards works by going to the end

of the list recursively before starting to print

each item

public void printBackwards(IntNode x)

if(x ! null) printBackwards(x.ge

tLink( )) System.out.println(x.getData(

))

How Print Linked List Works

When print(head) is called, an AR is placed on

the run-time stack which contains a pointer, x,

pointing at the first IntNode in the chain

Since x is not null, print out 6 and then call

print passing this nodes link field A new AR

is pushed onto the stack with a pointer x

pointing at the IntNode with 18 Since x is not

null, print out 18 and then call print passing

the nodes link field (pointer to 3) etc until

x points at 4, when print(x.getLink( )) is

called, the parameter is null, so the recursion

ends

public void print(IntNode x) if(x !

null) System.out.println(x.getDat

a( )) print(x.getLink( ))

Call print(head)

head

6 18 3

9 4

See if you can demonstrate that printBackwards

works correctly using the above linked list and

following the recursive calls

Other Recursive Methods Fibonacci

The Fibonacci sequence of numbers is 1, 1, 2, 3,

5, 8, 13, 21, 34, Each digit is the sum of the

previous 2 digits Compute the nth fibonacci

value Iterative method public int

fibonacci(int n) int fib1 0, fib2 0,

j, temp for(j2 j temp fib2 fib2 fib2 fib1

fib1 temp return fib2

Recursive method public int fibonacci(int n)

if(n IllegalArgumentException (n 0) else if (n 1 n 2) return

1 else return fib(n 1) fib(n 2)

Recursive Binary Search

Binary search are already uses a

divide-and-conquer approach so it is suitable

for recursion The idea is that the binary search

method will compute a mid point, compare the

middle item to what is being sought, and either

return the index of the mid point if the item is

the one being sought, or recursively call itself

with a smaller portion of the array

Iterative method public int binarySearch

(intArray a, int target, int n) int

first, last, middle middle (first last)

/ 2 while(target ! amiddle first last) if (target last middle 1 else if (target

amiddle) first middle 1 if

(first 1

Recursive method public int bs(intArray a, int

target, int first, int last) int middle

(first last) / 2 if (target

amiddle) return middle else if (last

amiddle) return bs(a, target,

first, middle 1) else return bs(a,

target, middle 1, last)

Complexity of Recursion

- So far, our determination of computational

complexity has been based on seeing how many

times a loop iterates - With recursion, we dont have loops
- What is the complexity of a recursive method?
- It is the complexity of the method depth of

recursion - Depth is the number of times the method is called

recursively - Consider recursive factorial
- the method has 1 if-else statement so its

complexity is O(1) but there are n 1 recursive

calls if the original parameter is n, so the

complexity is O(1 (n 1)) O(n) - For binary search
- The method has 1 assignment statement and a

nested if-else statement which is O(1). How many

times is the method called? In the worst case,

log n, so the complexity is O(log n) just like

the iterative version

Why Recursion?

- There are several significant problems with

recursion - mostly it is hard (especially for inexperienced

programmers) to think recursively - Why use it? It seems like there is always an

iterative solution to a problem that we can solve

recursively - Is there a difference in computational

complexity? No - Is there a difference in the efficiency of

execution? Yes, in fact, the recursive version

is usually less efficient because of having to

push ARs onto and pop ARs off of the run-time

stack, so iteration is quicker - Although you might notice that the recursive

versions use fewer or no local variables

Why Recursion?

- The answer to our question is predominantly

because it is easier to code a recursive solution

once one is able to identify that solution - Compare the code, the recursive code is usually

smaller, more concise, possibly even easier to

understand - But also, there are some problems that are very

difficult to solve without recursion - Those that require backtracking such as
- searching a maze for a path to an exit
- tree based operations (which you will see in CSC

364) - There are also some interesting sorting

algorithms that use recursion - we may look at these later in the semester, or

you will see them in 364

Tower of Hanoi

Towers with 4 disks

- This problem comes from history, monks in Viet

nam were asked to carry 64 gold disks from one

tower (stack) to another - Each disk is of a different size
- There are 3 stacks, a source stack, a destination

stack and an intermediate stack - A disk is placed on one of three stacks but no

disk can be placed on top of a smaller disk

How will the monks solve this problem? How

long will it take them? The easiest solution is

a recursive one

Solution

- The key to the solution is to notice that to move

any disk, we must first move the smaller disks

off of it, thus a recursive definition - Lets start with 1 disk
- Move 1 disk from start tower to destination tower
- To move 2 disks
- Move smaller disk from start tower to

intermediate tower, move larger disk from start

tower to final tower, move smaller disk from

intermediate tower to final tower - To move n disks
- Solve the problem for n 1 disks but use the

intermediate tower instead of the final tower - Move the biggest disk from start tower to final

tower - Solve the problem for n 1 disks but use the

intermediate tower instead of the start tower

The Solution Pictorially

Recursively move 3 disks using Intermediate

instead of final

Start Intermediate Final

Start Intermediate Final

Start Intermediate Final

Move disk 4 from start to finish

Recursively move 3 disks using Intermediate

instead of start

Solving the Problem with 3 Disks

In solving the problem with 4 disks, we have

to first solve the problem with 3 disks where

the 3 disks move from Start to Intermediate,

how?

Start Intermediate Final

Solve the 3 disks recursively Move the top 2

disks from Start to Final Move bottom (3rd)

disk from Start to Intermediate Move 2 disks

from Final to Intermediate How do you move 2

disks? Move top disk from Start to

Intermediate Move 2nd disk from Start to

Final Move top disk from Intermediate to Final

Start Intermediate Final

Towers Solution and Complexity

public void hanoi(int n, char source, char

intermediate, char destination) if (n

1) System.out.println(move disk from

source to destination) else

hanoi(n-1, source,

destination, intermediate)

System.out.println(move disk from source

to destination) hanoi(n-1,

intermediate, source, destination)

The complexity of this solution is not obvious

because this is no longer a solution in which the

recursive part calls itself once, but instead,

there are two recursive calls Lets find the

solution by considering the number of moves for n

2 hanoi(n1) println hanoi(n1) 1 1

1 3 println statements (3 disk moves) So, for

n 3 we have hanoi(n2) println hanoi(n2)

3 1 3 7 moves For n 4 we have hanoi(n3)

println hanoi(n3) 7 1 7 15 moves For

some n, we have hanoi(n-1) 1 hanoi(n-1) or 2

hanoi(n-1) 1 So, for n, the solution is twice

as much as n-1 This results in a complexity of

O(2n) how many moves will it take the monks

with n 64???

Tail Recursion

- Recall we could change the direction of

printing our linked list simply by altering

whether the recursion occurred before or after

the print statement - This is also true with the factorial problem
- return x factorial(x 1)
- return factorial(x 1) x
- Tail recursion occurs when the recursive call is

at the end of the recursive instruction - such as with the first of our factorial solutions

above - it is useful to note when your algorithm uses

tail recursion because in such a case, the

algorithm can usually be rewritten to use

iteration instead - this is not the case with head recursion, or when

the method calls itself recursively in different

places like the Tower of Hanoi solution - although we can also remove recursion from such

cases by using our own stack and essentially

simulating how recursion would work

Recursion and Objects

- A potential problem with recursion in Java occurs

when you are passing Objects recursively - Remember that if objects are pointed to by

reference variables (pointers) - Consider passing the head pointer of a linked

list to a recursive printBackward method - This will not cause a problem because the head

pointer itself is not being reassigned - But instead, consider not only passing the

reference variable, but also changing the Object

that is being pointed to - This will result in the Object or Objects in the

list being changed forever we need to avoid

this - In some languages, it is also possible to create

lost Objects if you are not careful in passing

pointers as parameters - In Java, this is not the case because Java does

not allow you to return an altered pointer, but

this could happen easily in C or Pascal!