Chapter 8: Recursion - PowerPoint PPT Presentation

1 / 28
About This Presentation
Title:

Chapter 8: Recursion

Description:

To solve the problem, break it down into a smaller version of the same problem ... Break down the smaller version into an even smaller version and solve this new ... – PowerPoint PPT presentation

Number of Views:120
Avg rating:3.0/5.0
Slides: 29
Provided by: NKU
Category:

less

Transcript and Presenter's Notes

Title: Chapter 8: Recursion


1
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

2
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
3
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)
4
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

5
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)
6
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

7
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
8
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

9
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

10
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
11
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
12
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
13
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

14
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 15
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(
))
16
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
17
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)

18
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)
19
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

20
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

21
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

22
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
23
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

24
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
25
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
26
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???
27
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

28
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!
Write a Comment
User Comments (0)
About PowerShow.com