0% found this document useful (0 votes)
27 views38 pages

Understanding Recursive Functions

Uploaded by

mohammadbaaboud
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
27 views38 pages

Understanding Recursive Functions

Uploaded by

mohammadbaaboud
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Data Structures

Recursion

Recursive Function Call


A recursive call is a function call in which the
called function is the same as the one making
the call.

In other words, recursion occurs when a


function calls itself!

We must avoid making an infinite sequence of


function calls (infinite recursion).

1
Finding a Recursive Solution
Each successive recursive call should bring you
closer to a situation in which the answer is known.
A case for which the answer is known (and can be
expressed without recursion) is called a base case.

Each recursive algorithm must have at least one


base case, as well as the general (recursive) case

Recursive Functions:
The General Format
if (some condition for which answer is known)
// base case
solution statement

else // general case

recursive function call

2
Recursion Types
There are three types of recursion, depending on the
number of recursive calls in a function:

Linear Recursion

Binary Recursion

Multiple Recursion

1. Linear Recursion
Test for base cases
Begin by testing for a set of base cases (there should be at least one).
Every possible chain of recursive calls must eventually reach a base
case, and the handling of each base case should not use recursion.

Recur once
Perform a single recursive call.
There may be a test that decides which of several possible recursive
calls to make, but should ultimately make just one of these calls.
Define each possible recursive call so that it makes progress towards
a base case.

3
Example 1:
The Factorial of n – Iterative Definition

Mathematically, the factorial function for


a positive integer, n >= 0, is defined as:

1, if n=0
n! =
n * (n-1) * … * 2 * 1, if n>0

Example 1:
The Factorial of n – Recursive Definition

Mathematically, the factorial function for


a positive integer, n >= 0, is defined as:

1, if n=0
n! =
n * (n-1)! , if n>0

4
Writing a Recursive Function for
the Factorial of n
The function call factorial(4) should have value
24, because that is 4 * 3 * 2 * 1 .

For a situation in which the answer is known,


the value of 0! is 1.

So the base case could be along the lines of

if ( number == 0 )
return 1;
9

Writing a Recursive Function for


the Factorial of n
Now for the general case . . .

The value of factorial(n) can be written as:


n * the product of the numbers from (n - 1) to 1
that is,
n * (n - 1) * . . . * 1

or, n * factorial(n - 1)

Notice that the recursive call factorial(n - 1) gets us closer


to the base case of factorial(0).

10

5
Example 1:
The Recursive Factorial Function
// Pre: number is assigned and number >= 0.
//
public int factorial ( int n ) {
if ( n == 0) // base case
return 1;
else // general case
return n * factorial ( n - 1 );
}

Performance: O (n).

11

Recursive Solution
Example:

12

6
Three-Question Method for
Verifying Recursive Functions
Base-Case Question: Is there a non-recursive way out of
the function?

Smaller-Case Question: Does each recursive function call


involve a smaller case of the original problem leading to
the base case?

General-Case Question: Assuming each recursive call


works correctly, does the whole function work correctly?

13

Example 2:
Where Recursion Comes Naturally
From mathematics, we know that:
20 = 1 and 25 = 2 * 24

In general,
x0 = 1 and xn = x * xn-1
for integer x, and integer n > 0.

Here we are defining xn recursively, in terms of xn-1

14

7
Example 2:
The Recursive Power Function
// Pre: n >= 0. x, n are not both zero
// Post: Function value = x raised to the power n.
//
public int power ( int x, int n ) {
if ( n == 0 )
return 1; // base case
else // general case
return ( x * power ( x , n-1 ) );
}
Performance: O (n).
Of course, an alternative would have been to use looping
instead of a recursive call in the function body.
15

Example 3: A More Efficient Method


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 = (24/2)2 = (22)2 = 42 = 16
25 = 2(24/2)2 = 2(22)2 = 2
2(4 ) = 32
26 = (26/2)2 = (23)2 = 82 = 64
27 = 2(26/2)2 = 2(23)2 = 2
2(8 ) = 128
16

8
Example 3:
Recursive Squaring Algorithm
// Pre: n >= 0. x, n are not both zero
// Post: Function value = x raised to the power n.
//
public int power ( int x, int n ) {
if ( n == 0 ) // base case
return 1;
if ( n%2 == 1 ) { // general case 1
y = Power (x, (n - 1)/ 2);
return x * y * y;
}
else { // general case 2
y = Power (x, n/ 2);
return y * y;
}
}
17

Example 3:
Recursive Squaring Analysis
// Pre: n >= 0. x, n are not both zero
// Post: Function value = x raised to the power n.
//
public int power ( int x, int n ) { Each time we make a
if ( n == 0 ) // base case recursive call we halve
return 1; the value of n; hence, we
if ( n%2 == 1 ) { // n is odd make log n recursive calls.
y = Power (x, (n - 1)/ 2); That is, this method runs
return x * y * y; in O (log n) time.
}
else { // n is even
y = Power (x, n/ 2); It is important that we
return y * y; use a variable twice here
} rather than calling the
} method twice.
18

9
Why Use Recursion?
The above examples could have been written without
recursion, using iteration instead.

The iterative solution uses a loop.

The recursive solution uses an if statement and the


run-time stack.

However, for certain problems the recursive solution


is the most natural solution. This often occurs when
reference variables are used.

19

Example 4:
A Singly Linked List
public class Node {
int info ;
Node next ;
}
public class SortedList {
private Node head ;
public
. . . // member functions

20

10
Example 4:
The ReversePrint Function

head

A B C D E

THEN, print FIRST, print out this section of list, backwards


this element

21

Example 4:
Base Case and General Case
A base case may be a solution in terms of a “smaller” list.
Certainly for a list with 0 elements, there is no more processing
to do.

Our general case needs to bring us closer to the base case


situation. That is, the number of list elements to be processed
decreases by 1 with each recursive call. By printing one
element in the general case, and also processing the smaller
remaining list, we will eventually reach the situation where 0 list
elements are left to be processed.

In the general case, we will print the elements of the smaller


remaining list in reverse order, and then print the currently
referenced element.

22

11
Example 4:
Using Recursion with a Linked List
// Pre: listHead points to an element of a list.
// Post: all elements of list pointed to by listHead have been printed
// out in reverse order.
public void reversePrint ( Node listHead ) {

if ( listHead != null ) { // general case

reversePrint ( [Link] ) ; // process the rest


[Link]( [Link]); // then print this element

}
// Base case : if the list is empty, do nothing
}

23

What Happens
When a Function is Called?
A transfer of control occurs from the calling block to the
code of the function.

It is necessary that there be a return to the correct place


in the calling block after the function code is executed.
This correct place is called the return address.

When any function is called, the run-time stack is used.


On this stack is placed an activation record (stack
frame) for the function call.

24

12
The Stack Activation Frames
The activation record stores:
• The return address for this function call
• The parameters
• The local variables
• The function’s return value, if non-void.

The activation record for a particular function call is


popped off the run-time stack when the final closing
brace in the function code is reached, or when a return
statement is reached in the function code.
At this time the function’s return value, if non-void, is
brought back to the calling block return address for use
there.

25

Example 5:
Another Recursive Function
// Pre: a and b have been assigned values
// Post: function value = ??
//
public int func ( int a, int b ) {
int result;
if ( b == 0 ) // base case
result = 0;
else if ( b > 0 ) // first general case
result = a + func ( a , b - 1 ) ) ; // instruction 50
else // second general case
result = func ( - a , - b ) ; // instruction 70
return result;
}

26

13
Run-Time Stack Activation Records
x = func(5, 2); // original call is instruction 100

functionVAL ?
result ? original call
b 2 at instruction 100
a 5 pushes on this record
Return Address 100 for func(5,2)
27

Run-Time Stack Activation Records


x = func(5, 2); // original call is instruction 100

functionVAL ? call in func(5,2) code


result ? at instruction 50
b 1 pushes on this record
a 5 for func(5,1)
Return Address 50
functionVAL ?
result 5+func(5,1) = ?
b 2 record for func(5,2)
a 5
Return Address 100
28

14
Run-Time Stack Activation Records
x = func(5, 2); // original call is instruction 100
fubctionVAL ? call in func(5,1) code
result ? at instruction 50
b 0 pushes on this record
a 5 for func(5,0)
Return Address 50
functionVAL ?
result 5+func(5,0) = ?
b 1
a 5 record for func(5,1)
Return Address 50
functionVAL ?
result 5+func(5,1) = ?
b 2
a 5 record for func(5,2)
Return Address 100
29

Run-Time Stack Activation Records


x = func(5, 2); // original call is instruction 100
functionVAL 0
result 0
b 0
a 5
record for func(5,0)
Return Address 50 is popped first
with its functionVAL
functionVAL ?
result 5+func(5,0) = ?
b 1
a 5
record for func(5,1)
Return Address 50
functionVAL ?
result 5+func(5,1) = ?
b 2
a 5
record for func(5,2)
Return Address 100
30

15
Run-Time Stack Activation Records
x = func(5, 2); // original call is instruction 100

fnctionVAL 5
result 5+func(5,0) = 5+ 0
b 1
a 5 record for func(5,1)
Return Address 50 is popped next
with its functionVAL
functionVAL ?
result 5+func(5,1) = ?
b 2
a 5 record for func(5,2)
Return Address 100
31

Run-Time Stack Activation Records


x = func(5, 2); // original call is instruction 100

functionVAL 10
result 5+func(5,1) = 5+5 record for func(5,2)
b 2 is popped last
a 5
with its functionVAL
Return Address 100
32

16
Example 6:
The Binary Search Function
Given a sorted list of n items and the key of an item, (Mustafa), find that
item’s position in the list.
The function binarySearch:
Assume the list is in array called data of size n,
public int binarySearch(E target, int first, int size) {
int middle; Position Key

if (size == 0) // The empty list case 0 Abdullah


return n; 1 Ahmad
else {
middle = first + size/2; 2 Ali
if (target == data[middle]) // The basic case
return middle; 3 Fareed
else 4 Mohammad
if (target < data[middle]) // Two general cases
binarySearch(target, first, size/2); 5 Mustafa
else
binarySearch(target, middle+1, (size-1)/2);
6 Tareq
} 7 Yaser
}

33

Example 6:
The Binary Search Function
Given a sorted list of n items and the key of an item, (Mustafa), find that
item’s position in the list.
The function binarySearch:
Assume the list is in array called data of size n,
public int binarySearch(E target, int first, int size) {
int middle; Position Key

if (size == 0) // The empty list case 0 Abdullah


return n; 1 Ahmad
else {
middle = first + size/2; 2 Ali
if (target == data[middle]) // The basic case
return middle; 3 Fareed
else 4 Mohammad
if (target < data[middle]) // Two general cases
binarySearch(target, first, size/2); 5 Mustafa
else
binarySearch(target, middle+1, (size-1)/2);
6 Tareq
} 7 Yaser
}

34

17
Example 6:
The Binary Search Function
Given a sorted list of n items and the key of an item, (Mustafa), find that
item’s position in the list.
The function binarySearch:
Assume the list is in array called data of size n,
public int binarySearch(E target, int first, int size) {
int middle; Position Key

if (size == 0) // The empty list case 0 Abdullah


return n; 1 Ahmad
else {
middle = first + size/2; 2 Ali
if (target == data[middle]) // The basic case
return middle;
3 Fareed
else 4 Mohammad
if (target < data[middle]) // Two general cases
binarySearch(target, first, size/2); 5 Mustafa
else
binarySearch(target, middle+1, (size-1)/2);
6 Tareq
} 7 Yaser
}

35

Example 6:
The Binary Search Function
Given a sorted list of n items and the key of an item, (Mustafa), find that
item’s position in the list.
The function binarySearch:
Assume the list is in array called data of size n,
public int binarySearch(E target, int first, int size) {
int middle; Position Key

if (size == 0) // The empty list case 0 Abdullah


return n; 1 Ahmad
else {
middle = first + size/2; 2 Ali
if (target == data[middle]) // The basic case
return middle; 3 Fareed
else 4 Mohammad
if (target < data[middle]) // Two general cases
binarySearch(target, first, size/2); 5 Mustafa
else
binarySearch(target, middle+1, (size-1)/2);
6 Tareq
} 7 Yaser
}

36

18
Example 6:
The Binary Search Function
Given a sorted list of n items and the key of an item, (Mustafa), find that
item’s position in the list.
The function binarySearch:
Assume the list is in array called data of size n,
public int binarySearch(E target, int first, int size) {
int middle; Position Key

if (size == 0) // The empty list case 0 Abdullah


return n; 1 Ahmad
else {
middle = first + size/2; 2 Ali
if (target == data[middle]) // The basic case
return middle;
3 Fareed
else 4 Mohammad
if (target < data[middle]) // Two general cases
binarySearch(target, first, size/2); 5 Mustafa
else
binarySearch(target, middle+1, (size-1)/2);
6 Tareq
} 7 Yaser
}

37

Example 6:
The Binary Search Function
Given a sorted list of n items and the key of an item, (Mustafa), find that
item’s position in the list.
The function binarySearch:
Assume the list is in array called data of size n,
public int binarySearch(E target, int first, int size) {
int middle; Position Key

if (size == 0) // The empty list case 0 Abdullah


return n; 1 Ahmad
else {
middle = first + size/2; 2 Ali
if (target == data[middle]) // The basic case
return middle; 3 Fareed
else 4 Mohammad
if (target < data[middle]) // Two general cases
binarySearch(target, first, size/2); 5 Mustafa
else
binarySearch(target, middle+1, (size-1)/2);
6 Tareq
} 7 Yaser
}

38

19
Example 6:
The Binary Search Function
Given a sorted list of n items and the key of an item, (Mustafa), find that
item’s position in the list.
The function binarySearch:
Assume the list is in array called data of size n,
public int binarySearch(E target, int first, int size) {
int middle; Position Key

if (size == 0) // The empty list case 0 Abdullah


return n; 1 Ahmad
else {
middle = first + size/2; 2 Ali
if (target == data[middle]) // The basic case
return middle; 3 Fareed
else 4 Mohammad
if (target < data[middle]) // Two general cases
binarySearch(target, first, size/2); 5 Mustafa
else
binarySearch(target, middle+1, (size-1)/2);
6 Tareq
} 7 Yaser
}

39

2. Binary Recursion
Binary recursion occurs whenever there are two recursive calls
for each non-base case.

Example from textbook: the drawInterval method for drawing


ticks on an English ruler.

40

20
Recursion Efficiency,
Example 7: Fibonacci Numbers
Fibonacci numbers are defined by the recurrence relation:

F0 = 0,
F1 = 1,
Fn = Fn-1 + Fn-2 for n >= 2.

41

Example 7:
The Recursive Fibonacci Method
// BinaryFib: recursive version
// Pre: The parameter n is a nonnegative integer.
// Post: The function returns the nth fibonacci number.
//
public int BinaryFib (int n) {
if (n <= 0) return 0; // The basic case
else if (n == 1) return 1; // Another basic case
else return BinaryFib(n-1) + BinaryFib(n-2);
}

42

21
Analysis of Recursive Fibonacci
Let kn be the number of calls to BinaryFib, then:
• k0 = 1 and k1 = 1
• k2 = 1 + k1 + k0 =1+1+1=3
• k3 = 1 + k2 + k1 =1+3+1=5
• k4 = 1 + k3 + k2 =1+5+3=9
• k5 = 1 + k4 + k3 = 1 + 9 + 5 = 15
• k6 = 1 + k5 + k4 = 1 + 15 + 9 = 25
• k7 = 1 + k6 + k5 = 1 + 25 + 15 = 41
• k8 = 1 + k7 + k6 = 1 + 41 + 25 = 67.
Note that kn more than doubles every two consecutive ns.
That is, kn > 2n/2. It is exponential!
43

Fibonacci Numbers and the


Recursive Function Efficiency
The number of
operations
needed to
calculate F7 can
be counted from
the recursion
tree shown

We need 20 add
operations.

O(1.7n)
44

22
A Much Better
Recursive Fibonacci Algorithm
Use linear recursion instead:
// LinearFib: Linear recursive version
// Pre: The parameter n is a nonnegative integer.
// Post: Returns a pair of fibonacci numbers (Fn , Fn-1).
//
public long[ ] LinearFib (int n) {
if ( n <= 1 ) {
long[ ] answer = {n, 0};
return answer;
}
else {
long[ ] temp = LinearFib (n - 1);
long[ ] answer = {temp[0] + temp[1], temp[0]};
return answer;
}
}
LinearFib makes n-1 recursive calls – O (n).
45

Fibonacci Numbers
A Non-Recursive Version
// fibonacci: iterative version
// Pre: The parameter n is a nonnegative integer.
// Post: The function returns the nth fibonacci number.
//
public int fibonacci (int n) {
int last_but_one; // second previous Fibonacci number, Fi-2
int last_value; // previous Fibonacci number, Fi-1
int current; // current Fibonacci number Fi

if (n <= 0) return 0; // The basic case


else if (n == 1) return 1; // Another basic case

46

23
Fibonacci Numbers
A Non-Recursive Function
else {
last_but_one = 0;
last_value = 1;

// The iterative solution of the general case


for (int i=2; i <= n; i++) {
current = last_but_one + last_value;
last_but_one = last_value;
last_value = current;
}
return current;
}
}
47

Fibonacci Numbers
Non-Recursive Function efficiency
The number of operations needed to calculate F7
can be counted from the algorithm.

The loop is executed n-1 times, and has only one


add operation inside of it.

We need only 6 add operations.

O(n)

48

24
3. Multiple Recursion
Where a method may make more than two
recursive calls.
• makes potentially many recursive calls
• not just one or two

Example:
Read the recursive method for analyzing the disk
space usage of a file system in Section 5.1.4) of
the textbook.

49

Efficiency of Recursion
Recursion is not efficient because:
• It may involve much more operations than necessary.
Time complexity.
• It uses the run-time stack, which involves pushing and popping a
lot of data in and out of the stack, some of it may be unnecessary.
Time and Space complexity.

Both the time and space complexities of recursive functions


may be considerably higher than their iterative alternatives

DO NOT USE RECURSION unless necessary

Try to convert recursive functions into non-recursive ones


Conversion may involve using an external stack
50

25
Comments on Recursion
There are several common applications of recursion
where a corresponding iterative solution may not be
obvious or easy to develop.

Classic examples of such include, Towers of Hanoi,


multidimensional searching and backtracking.

However, many common textbook examples of recursion


are tail-recursive, i.e. the last statement in the recursive
function is a recursive invocation.

51

Tail Recursion
The case in which a function contains only a
single recursive call and it is the last statement
to be executed in the function.

Tail recursion can be replaced by iteration to


remove recursion from the solution as in the
factorial function.

52

26
Example 8:
Reversing an Array Recursively
Algorithm reverseArray(A, i, j):
Input: An array A and nonnegative integer indices i and j
Output: Reversal of the elements in A between indices i & j inclusive.

if i < j then
Swap A[ i ] and A[ j ]
reverseArray(A, i + 1, j - 1)
return
This algorithm is tail recursive because:
1. It is of the linear recursive type,
2. The recursive call is at the last statement.

53

Example 8:
Removing Tail Recursion
Tail recursive methods can be easily converted to non-
recursive methods (which saves on some resources).
The reverseArray algorithm can be written as:
Algorithm iterativeReverseArray(A, i, j):
Input: An array A and nonnegative integer indices i and j
Output: Reversal of the elements in A between indices i & j inclusive.
while i < j do
Swap A[ i ] and A[ j ]
i =i+1
j =j–1
return
54

27
Conclusion:
Use a Recursive Solution when:
The depth of recursive calls is relatively “shallow”
compared to the size of the problem. (factorial is deep)

The recursive version does about the same amount of work


as the non-recursive version. (fibonacci does more work)

The recursive version is shorter and simpler than the non-


recursive solution. (towers of hanoi)

SHALLOW DEPTH EFFICIENCY CLARITY

55

Example 9:
The Towers of Hanoi Problem
A B C

The Problem:
You have n disks, as shown,
and you need to move them
from tower A to tower C using tower B as temporary.
i.e. moveDisks (n,A,C,B)

The Rules:
Move only one disk at a time
No larger disk can be on top of a smaller disk
56

28
Attempted Solution
For n=1:

A B C

57

Attempted Solution
For n=1:
• move (A,C)

A B C

58

29
Attempted Solution
For n=2:

A B C

59

Attempted Solution
For n=2:
• move (A,B)

A B C

60

30
Attempted Solution
For n=2:
• move (A,B)
• move (A,C)
A B C

61

Attempted Solution
For n=2:
• move (A,B)
• move (A,C)
• move (B,C)
A B C

62

31
Attempted Solution
For n=3:

A B C

63

Attempted Solution
For n=3:
• move (A,C)

A B C

64

32
Attempted Solution
For n=3:
• move (A,C)
• move (A,B)

A B C

65

Attempted Solution
For n=3:
• move (A,C)
• move (A,B)
• move (C,B)

A B C

66

33
Attempted Solution
For n=3:
• move (A,C) Effectively, we moved 2 disks
• move (A,B) from A to B using C as temporary.
• move (C,B) i.e. moveDisks (2,A,B,C)

A B C

67

Attempted Solution
For n=3:
• move (A,C) Effectively, we moved 2 disks
• move (A,B) from A to B using C as temporary.
• move (C,B) i.e. moveDisks (2,A,B,C)
• move (A,C)
A B C

68

34
Attempted Solution
For n=3:
• moveDisks (2,A,B,C) Moved n-1 disks from A to B; temp C.
• move (A,C) Moved one disk from A to C,
this is the basic case

A B C

69

Attempted Solution
For n=3:
• moveDisks (2,A,B,C) Moved n-1 disks from A to B; temp C.
• move (A,C) Moved one disk from A to C.
• move (B,A)

A B C

70

35
Attempted Solution
For n=3:
• moveDisks (2,A,B,C) Moved n-1 disks from A to B; temp C.
• move (A,C) Moved one disk from A to C.
• move (B,A)
• move (B,C)
A B C

71

Attempted Solution
For n=3:
• moveDisks (2,A,B,C) Moved n-1 disks from A to B; temp C.
• move (A,C) Moved one disk from A to C.
• move (B,A)
• move (B,C)
A B C
• move (A,C)

72

36
Attempted Solution
For n=3:
• moveDisks (2,A,B,C) Moved n-1 disks from A to B; temp C.
• move (A,C) Moved one disk from A to C.
• move (B,A) Moved n-1 disks from B to C; temp A.
• move (B,C)
A B C
• move (A,C)

73

Attempted Solution
For n=3:
• moveDisks (2,A,B,C) Moved n-1 disks from A to B; temp C.
• move (A,C) Moved one disk from A to C.
• moveDisks (2,B,C,A) Moved n-1 disks from B to C; temp A.

A B C

74

37
Recursive Function for Solving the
Towers of Hanoi Problem – O(2n)
public static void moveDisks(int n, char s, char d, char t) {
if (n <= 1) { // base case
[Link]("Move the top disk from “);
[Link](s + " to " + d);
}
else { // general case
moveDisks(n-1, s, t, d);
moveDisks(1, s, d, t);
moveDisks(n-1, t, d, s);
}
}
75

38

You might also like