0% found this document useful (0 votes)
2 views

L6_Recursion

The document provides an overview of recursion, a powerful problem-solving technique that breaks problems into smaller, identical problems until reaching a base case. It discusses various types of recursion, including direct, indirect, linear, tree, and tail recursion, along with their characteristics and examples. Additionally, it highlights the pros and cons of recursion, its applications such as the Tower of Hanoi, and includes a binary search example with worst-case analysis.

Uploaded by

rounak15jais24
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views

L6_Recursion

The document provides an overview of recursion, a powerful problem-solving technique that breaks problems into smaller, identical problems until reaching a base case. It discusses various types of recursion, including direct, indirect, linear, tree, and tail recursion, along with their characteristics and examples. Additionally, it highlights the pros and cons of recursion, its applications such as the Tower of Hanoi, and includes a binary search example with worst-case analysis.

Uploaded by

rounak15jais24
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 55

Recursion

Recursion
Recursion

Direct Indirect Linear Tree Tail


Recursion
Powerful Problem-Solving Technique
Breaks a problem into smaller, identical
problems
Break each of these smaller problems
into even smaller, identical problems
Eventually arrive at a stopping case
The Base Case
Problem cannot be broken down any
farther
Counting Recursively

Counting Down to Zero from a Number


Say the number
Ask a friend to count down from the number minus
one
When someone says zero, stop.
6 5 4 3 2 1
You count You count You count You count You countYou count
.
down from 5down from 4.down from 3.
down from 2. 1. from 0.
down fromdown 0!
Tracing Recursion

Each recursive call assigns new


values to the parameters and local void countDown(int number) {
if (number == 0) {
variables. printf("%d\n", number);
} else {
printf("%d\n", number);
countDown(number - 1);
countdown(5) }
countdown(4) }
number is assigned countdown(3)
5
APPLICATION
number is assigned countdown(2)
4
STACK
number is assigned countdown(1)
Display 5 OR
3 number is assigned countdown(0)
Call countdown(4) DisplayCALL 4 STACK
2 number is assigned
Call countdown(3) Display 3 1 number is assigned
Call countdown(2) Display 2 0
ACTIVATION Call countdown(1) Display 1
RECORD Call countdown(0)Display 0
Recursion and Iteration

Recursive Solutions
Usually involve branching void countDown(int number) {
if (number == 0) {

Recursive
Solution
if and switch statements printf("%d\n", number);
} else {
Deciding whether or not it has printf("%d\n", number);
the base case countDown(number - 1);
}
May involve a loop in addition to }
branching
void countDown(int number) {
Iterative Solutions
while (number >= 0) {
printf("%d\n", number);
Involve loops
Solution
Iterative
number = number - 1;
} // end while
while, for and do-while }
statements
Recursion
• Recursion is an implicit application of STACK ADT.
• A recursive function is a function that calls itself to solve a smaller
version of its task until a final call is made which does not require a
call to itself.
• Every recursive solution has two major cases:
– the base case in which the problem is simple enough to be solved directly
without making any further calls to the same function.

– Recursive case, in which first the problem at hand is divided


into simpler subparts. Second the function calls itself but with
subparts of the problem obtained in the first step. Third, the
result is obtained by combining the solutions of simpler sub-
parts.
Types of Recursion
• Any recursive function can be characterized based on:

 whether the function calls itself directly or indirectly (direct or


indirect recursion).

 whether any operation is pending at each recursive call (tail-


recursive or not).

 the structure of the calling pattern (linear or tree-recursive).

Recursion

Direct Indirect Linear Tree Tail


Direct Recursion
• A function is said to be directly recursive if it explicitly calls itself.
• For example, consider the function given below.

int Func( int n)


{
if ( n == 0 )
retrun n;
return Func(n-1);
}
Indirect Recursion
• A function is said to be indirectly recursive if it contains a call to
another function which ultimately calls it.
• Look at the functions given below. These two functions are
indirectly recursive as they both call each other.

int Func1(int n) int Func2(int x)


{ {
if(n==0) return Func1(x-1);
return n; }
return Func2(n);
}
Indirect Recursion
• A function is said to be indirectly recursive if it contains a call to
another function which ultimately calls it.
.
void functionA(int n) {
if (n > 0) {
printf("%d ", n);
functionB(n - 1);
}
}
void functionB(int n) {
if (n > 0) {
printf("%d ", n - 1);
functionA(n - 1);
}
}
Linear Recursion
• Recursive functions can also be characterized depending on the
way in which the recursion grows: in a linear fashion or forming a
tree structure.
• In simple words, a recursive function is said to be linearly recursive
when no pending operation involves another recursive call to the
function.
• For example, the factorial function is linearly recursive as the
pending operation involves only multiplication to be performed
and does not involve another call to fact() function.
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}
public static int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}

int factorial(int 2)
{
int fact;
if (n > 1)
fact = factorial(1) * 2;
else
fact = 1;
return fact;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}

int factorial(int 2)
{
int fact;
if (n > 1)
fact = factorial(1) * 2;
else
fact = 1;
return fact;
}

int factorial(int 1)
{
int fact;
if (n > 1)
fact = factorial(n - 1) * n;
else
fact = 1;
return fact;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}

int factorial(int 2)
{
int fact;
if (n > 1)
fact = factorial(1) * 2;
else
fact = 1;
return fact;
}

int factorial(int 1)
{
int fact;
if (n > 1)
fact = factorial(n - 1) * n;
else
fact = 1;
return 1;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}

int factorial(int 2)
{
int fact;
if (n > 1)
fact = 1 * 2;
else
fact = 1;
return fact;
}

int factorial(int 1)
{
int fact;
if (n > 1)
fact = factorial(n - 1) * n;
else
fact = 1;
return 1;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}

int factorial(int 2)
{
int fact;
if (n > 1)
fact = 1 * 2;
else
fact = 1;
return 2;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = 2 * 3;
else
fact = 1;
return fact;
}

int factorial(int 2)
{
int fact;
if (n > 1)
fact = 1 * 2;
else
fact = 1;
return 2;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = 2 * 3;
else
fact = 1;
return 6;
}
Tree Recursion
• A recursive function is said to be tree recursive (or non-linearly
recursive) if the pending operation makes another recursive call to
the function.
• For example, the Fibonacci function Fib in which the pending
operations recursively calls the Fib function.

int Fibonacci(int num)


{
if(num <= 2)
return 1;
return ( Fibonacci (num - 1) + Fibonacci(num – 2));
}
Fibonacci Series
• The Fibonacci series can be given as:
1 1 2 3 5 8 13 21 34 55……
int Fibonacci(int num) {
if(num <= 2) return 1;
return ( Fibonacci (num - 1) + Fibonacci(num – 2));
}
FIB(7)

FIB(6) FIB(5)

FIB(5) FIB(4) FIB(4) FIB(3)

FIB(4) FIB(3) FIB(3) FIB(2) FIB(3) FIB(2) FIB(2) FIB(1)

FIB(3) FIB(2) FIB(2) FIB(1) FIB(2) FIB(1) FIB(2) FIB(1)

FIB(2) FIB(1)
Tail Recursion
• A recursive function is said to be tail recursive if no operations are
pending to be performed when the recursive function returns to its
caller.
• That is, when the called function returns, the returned value is
immediately returned from the calling function.
• Tail recursive functions are highly desirable because they are much
more efficient to use as in their case, the amount of information
that has to be stored on the system stack is independent of the
number of recursive calls.
int factorial(int n) int Fact1(int n, int res)
{ {
if (n == 0 || n == 1) if (n==1)
return 1; return res;
return n * factorial(n - 1); return Fact1(n-1, n*res);
} }
Tail Recursion-fib
int fibonacci_tail(int n, int a, int b) {
if (n == 0) return a;
if (n == 1)
return b;
return fibonacci_tail(n - 1, b, a + b);
}

int Fact1(int n, int res)


{
if (n==1)
return res;
return Fact1(n-1, n*res);
}
PrintReverse
void PrintReverse(int arr[], int i, int j) {
if (i > j) return;

if (i == j) {printf("%d", arr[i]);
} else {
int mid = (i + j) / 2;
PrintReverse(arr, mid + 1, j);
PrintReverse(arr, i, mid);
}
}
Pros and Cons of Recursion
Pros
• Recursive solutions often tend to be shorter and simpler than non-
recursive ones.
• Code is clearer and easier to use.
• Follows a divide and conquer technique to solve problems.
• In some (limited) instances, recursion may be more efficient.
Cons
• For some programmers and readers, recursion is a difficult
concept.
• Recursion is implemented using system stack. If the stack space on
the system is limited, recursion to a deeper level will be difficult to
implement.
• Aborting a recursive process in midstream is slow.
• Using a recursive function takes more memory and time to execute
as compared to its non-recursive counterpart.
• It is difficult to find bugs, particularly when using global variables.
Tower of Hanoi
Tower of Hanoi is one of the main applications of a recursion. It says, "if you can solve
n-1 cases, then you can easily solve the nth case"

A B C A B C

If there is only one ring, then simply move the ring from source to the destination

A B C
A B C A B C

If there are two rings, then first move ring 1 to the spare
pole and then move ring 2 from source to the
destination. Finally move ring 1 from the source to the
A B C
destination
Tower of Hanoi
Consider the working with three rings.

A B C
A B C
A B C

A B C A B C
A B C

A B C A B C
Recursive Algorithms
Binary Search Example

target is 33
The array a looks like this:
Indices 0 1 2 3 4 5 6 7 8 9
Contents 5 7 9 13 32 33 42 54 56 88

mid = (0 + 9) / 2 (which is 4)
33 > a[mid] (that is, 33 > a[4])
So, if 33 is in the array, then 33 is one of:
5 6 7 8 9
33 42 54 56 88

Eliminated half of the remaining elements from


consideration because array elements are sorted.
target is 33 Binary Search Example
The array a looks like this:
Indexes 0 1 2 3 4 5 6 7 8 9
Contents 5 7 9 13 32 33 42 54 56 88

mid = (5 + 9) / 2 (which is 7)
33 < a[mid] (that is, 33 < a[7]) Eliminate
So, if 33 is in the array, then 33 is one of: half of the
5 6 remaining
33 42 elements

mid = (5 + 6) / 2 (which is 5)
33 == a[mid]
So we found 33 at index 5:
5
33
Worst-case Analysis
• Item not in the array (size N)
• T(N) = number of comparisons with array elements
• T(1) = 1
• T(N) = 1 + T(N / 2)
Worst-case Analysis
• Item not in the array (size N)
• T(N) = number of comparisons with array elements
• T(1) = 1
• T(N) = 1 + T(N / 2)
= 1 + [1 + T(N / 4)]
Worst-case Analysis
• Item not in the array (size N)
• T(N) = number of comparisons with array elements
• T(1) = 1
• T(N) = 1 + T(N / 2)
= 1 + [1 + T(N / 4)]
= 2 + T(N / 4)
= 2 + [1 + T(N / 8)]
Worst-case Analysis
• Item not in the array (size N)
• T(N) = number of comparisons with array elements
• T(1) = 1
• T(N) = 1 + T(N / 2) 
= 1 + [1 + T(N / 4)]
= 2 + T(N / 4) 
= 2 + [1 + T(N / 8)]
= 3 + T(N / 8) 

=…
Worst-case Analysis
• Item not in the array (size N)
• T(N) = number of comparisons with array elements
• T(1) = 1
• T(N) = 1 + T(N / 2) 
= 1 + [1 + T(N / 4)]
= 2 + T(N / 4) 
= 2 + [1 + T(N / 8)]
= 3 + T(N / 8) 
=…
= k + T(N / 2k ) [1]
Worst-case Analysis
• T(N) = k + T(N / 2k ) [1]
• T(N / 2k ) gets smaller until the base case: T(1)
– 2k = N
– k = log2N
• Replace terms with k in [1]:
T(N) = log2N + T(N / N)
= log2N + T(1)
= log2N + 1
• “log2N” algorithm
• We used recurrence equations
Merge Sort—
A Recursive Sorting Algorithm
• Example of divide and conquer algorithm
• Recursive design:
– Divides array in half and merge sorts the
halves
– Combines two sorted halves
– Array has only one element (base case)
• Harder to implement iteratively
Execution Trace
3 6 8 2 5 4 7 1

3 6 8 2 5 4 7 1

3 6 8 2 5 4 7 1
3 6 8 2 5 4 7 1
Execution Trace
1 2 3 4 5 6 7 8

2 3 6 8 1 4 5 7

3 6 2 8 4 5 1 7

3 6 8 2 5 4 7 1
Merging Two Sorted Arrays
2

3 6 2 8
Merging Two Sorted Arrays
2 3

3 6 2 8
Merging Two Sorted Arrays
2 3 6

3 6 2 8
Merging Two Sorted Arrays
2 3 6 8

3 6 2 8
Worst-case Theoretical Analysis
• Comparisons of array elements
• None during decomposition
• Only during merging two sorted arrays

– To get an array of size N from two sorted


arrays of size N/2
(N – 1) comparisons (worst case: the largest
two elements are in different halves)
Analysis: Array of size N
• Let T(N) be the number of comparisons
• T(1) = 0
• T(N) = 2 T(N / 2) + (N – 1)
Analysis: Array of size N
• Let T(N) be the number of comparisons
• T(1) = 0
• T(N) = 2 T(N / 2) + (N – 1)
= 2 [2 T(N / 4) + (N / 2 – 1)] + (N – 1)
Analysis: Array of size N
• Let T(N) be the number of comparisons
• T(1) = 0
• T(N) = 2 T(N / 2) + (N – 1)
= 2 [2 T(N / 4) + (N / 2 – 1)] + (N – 1)
= 4 T(N / 4) + (N – 2) + (N – 1)
= 4 [ 2 T(N / 8) + (N / 4 – 1) ] + (N – 2) + (N – 1)
Analysis: Array of size N
• Let T(N) be the number of comparisons
• T(1) = 0
• T(N) = 2 T(N / 2) + (N – 1) 
= 2 [2 T(N / 4) + (N / 2 – 1)] + (N – 1)
= 4 T(N / 4) + (N – 2) + (N – 1) 
= 4 [ 2 T(N / 8) + (N / 4 – 1) ] + (N – 2) + (N – 1)
= 8 T(N / 8) + (N – 4) + (N – 2) + (N – 1) 
Analysis: Array of size N
• Let T(N) be the number of comparisons
• T(1) = 0
• T(N) = 2 T(N / 2) + (N – 1) 
= 2 [2 T(N / 4) + (N / 2 – 1)] + (N – 1)
= 4 T(N / 4) + (N – 2) + (N – 1) 
= 4 [ 2 T(N / 8) + (N / 4 – 1) ] + (N – 2) + (N – 1)
= 8 T(N / 8) + (N – 4) + (N – 2) + (N – 1) 
= 8 T(N / 8) + 3N – (1 + 2 + 4)
Analysis: Array of size N
• Let T(N) be the number of comparisons
• T(1) = 0
• T(N) = 2 T(N / 2) + (N – 1) 
= 2 [2 T(N / 4) + (N / 2 – 1)] + (N – 1)
= 4 T(N / 4) + (N – 2) + (N – 1) 
= 4 [ 2 T(N / 8) + (N / 4 – 1) ] + (N – 2) + (N – 1)
= 8 T(N / 8) + (N – 4) + (N – 2) + (N – 1) 
= 8 T(N / 8) + 3N – (1 + 2 + 4)
=…
= 2k T(N / 2k ) + kN – (1 + 2 + … 2k-1 ) [1]
Analysis Continued
• T(N) = 2k T(N / 2k ) + kN – (1 + 2 + … 2k-1 ) [1]
= 2k T(N / 2k ) + kN – (2k - 1) [2]
• T(N / 2k ) gets smaller until the base case T(1):
– 2k = N
– k = log2N
• Replace terms with k in [2]:
T(N) = N T(N / N) + log2N*N – (N – 1)
= N T(1) + Nlog2N – (N – 1)
= Nlog2N – N + 1
• “Nlog2N” algorithm
Geometric Series and Sum
• 1 + 2 + 4 + 8 + … + 2k
– 1+2=3
– 1+2+4=7
– 1 + 2 + 4 + 8 = 15
Geometric Series and Sum
• 1 + 2 + 4 + 8 + … + 2k
– 1+2=3 (4 – 1)
– 1+2+4=7 (8 – 1)
– 1 + 2 + 4 + 8 = 15 (16 – 1)
Geometric Series and Sum
• 1 + 2 + 4 + 8 + … + 2k
– 1+2=3 (4 – 1)
– 1+2+4=7 (8 – 1)
– 1 + 2 + 4 + 8 = 15 (16 – 1)
• 1 + 2 + 4 + 8 + … + 2k
= 2k+1 - 1
• 1 + r + r2 + r3 + … + rk
= r0 + r1 + r2 + r 3 + … + rk
= (rk+1 – 1) / (r – 1) [for r > 1]

You might also like