PPS UNIT-6
PPS UNIT-6
RECURSION
Recursion: Recursion is the technique of making a function call itself. This technique
provides a way to break complicated problems down into simple problems which are
easier to solve.
Recursion cannot be applied to all the problem, but it is more useful for the tasks that can
be defined in terms of similar subtasks. For Example, recursion may be applied to
sorting, searching, and traversal problems.
A physical world example would be to place two parallel mirrors facing each other. Any
object in between them would be reflected recursively.
Types of Recursion: There are primarily two types of C recursion:
1. Direct Recursion: In this type of recursion, a function calls itself directly. It is the
most common form of recursion, where a function invokes itself during its execution.
2. Indirect Recursion: In this type recursion, multiple functions call each other in a
circular manner. A function calls another function, which in turn may call the first one or
another function, creating a chain of recursive calls.
How recursion works?
Syntax:
This is how a general recursive function looks like −
In the following example, recursion is used to calculate the factorial of a
number
#include <stdio.h>
#include<conio.h>
int fact (int);
int main()
{ int n,f;
printf("Enter the number whose factorial you want to calculate?");
scanf("%d",&n);
f = fact(n);
printf("factorial = %d",f);
getch();
return 0;
}
int fact(int n)
{
if (n==0)
{
return 0;
}
else if ( n == 1)
{
return 1;
}
else
{
return n*fact(n-1);
}
}
We can understand the above program of the recursive method call by the figure given
below:
Program to find the nth term of the Fibonacci Series using Recursion.
#include<stdio.h>
int fibonacci(int);
void main ()
{
int n,f;
printf("Enter the value of n?");
scanf("%d", &n);
f = fibonacci(n);
printf("%d",f);
}
int fibonacci (int n)
{
if (n==0)
{
return 0;
}
else if (n == 1)
{
return 1;
}
else
{
return fibonacci(n-1)+fibonacci(n-2);
}
}
Memory allocation of Recursive method
Each recursive call creates a new copy of that method in the memory. Once some data is
returned by the method, the copy is removed from the memory. Since all the variables
and other stuff declared inside function get stored in the stack, therefore a separate stack
is maintained at each recursive call. Once the value is returned from the corresponding
function, the stack gets destroyed. Recursion involves so much complexity in resolving
and tracking the values at each recursive call. Therefore we need to maintain the stack
and track the values of the variables defined in the stack.
Let us consider the following example to understand the memory allocation of the
recursive functions.
Explanation: Let us examine this recursive function for n = 4. First, all the stacks are
maintained which prints the corresponding value of n until n becomes 0, Once the
termination condition is reached, the stacks get destroyed one by one by returning 0 to its
calling stack. Consider the following image for more information regarding the stack
trace for the recursive functions.
Advantages of Recursion:
Simplicity: Code gets cleaner and uses fewer needless function calls.
Elegant solution: Helpful for resolving difficult algorithms and formula-based
challenges.
Tractable approach: They are useful for traversing trees and graphs because
they are naturally recursive.
Code reusability: Recursive functions in C can be used repeatedly within the
program for different inputs, promoting code reusability.
Disadvantages of Recursion:
Complexity: It gets challenging to read and decipher the code.
Stack consumption: The copies of recursive functions take up a significant
amount of memory.
Performance: Recursive calls can be slower and less efficient than iterative
solutions due to function call overhead and repeated calculations.
Limited applicability: Not all problems are well-suited for C recursion, and
converting iterative solutions into recursive ones may not always be practical or
beneficial.
Ackermann function
The Ackermann function, named after the German mathematician Wilhelm Ackermann,
is a recursive mathematical function that takes two non-negative integers as inputs and
produces a non-negative integer as its output. Recursion is used to implement the
Ackermann function in C.
Ackermann algorithm:
Ackermann(m, n)
{next and goal are arrays indexed from 0 to m, initialized so that next[O] through next[m]
are 0, goal[O] through goal[m - l] are 1, and goal[m] is -1}
repeat
value ← next[O] + 1
transferring ← true
current ← O
while transferring do begin
if next[current] = goal[current] then goal[current] ← value
else transferring ← false
next[current] ← next[current]+l
current ← current + 1
end while
until next[m] = n + 1
return value {the value of A(m, n)}
end Ackermann
Quick sort
Quick Sort is a divide and conquer algorithm. This selects a pivot element and divides the
array into two subarrays
Step 1: Pick an element from an array, and call it a pivot element.
Step 2: Divide an unsorted array element into two arrays.
Step 3: If the value less than the pivot element comes under the first sub-array,
the remaining elements with a value greater than the pivot come in the second
sub-array.
How it works:
1. Choose a value in the array to be the pivot element.
2. Order the rest of the array so that lower values than the pivot element are on the
left, and higher values are on the right.
3. Swap the pivot element with the first element of the higher values so that the
pivot element lands in between the lower and higher values.
4. Do the same operations (recursively) for the sub-arrays on the left and right side
of the pivot element.
Example: Quicksort algorithm
Step 1: We start with an unsorted array.
[ 11, 9, 12, 7, 3]
Step 3: The rest of the values in the array are all greater than 3, and must be on the right
side of 3. Swap 3 with 11.
[ 3, 9, 12, 7, 11]
Step 4: Value 3 is now in the correct position. We need to sort the values to the right of
3. We choose the last value 11 as the new pivot element.
[ 3, 9, 12, 7, 11]
Step 5: The value 7 must be to the left of pivot value 11, and 12 must be to the right of it.
Move 7 and 12.
[ 3, 9, 7, 12, 11]
Step 6: Swap 11 with 12 so that lower values 9 and 7 are on the left side of 11, and 12 is
on the right side.
[ 3, 9, 7, 11, 12]
Step 7: 11 and 12 are in the correct positions. We choose 7 as the pivot element in sub-
array [ 9, 7], to the left of 11.
[ 3, 9, 7, 11, 12]
EXAMPLE-2:
Now,
The pivot is in a fixed position.
All the remaining elements are less.
The right elements are greater than the pivot.
Now, divide the array into 2 sub-arrays, the left and right parts.
Take the left partition and apply a quick sort.
Now,
The pivot is in a fixed position.
All the remaining elements are fewer and sorted
The right elements are greater and are in sorted order.
The final sorted list combines two sub-arrays 2, 3, 4, 5, 6, 7
Time Complexity : The speed of an algorithm is often captured by its time
complexity, which expresses the relationship between the input size and the time required
int main() {
int arr[] = { 12, 17, 6, 25, 1, 5 };
int n = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, n - 1);
printf("Sorted array: \n");
printArray(arr, n);
return 0;
}
Output Sorted array: 1 5 6 12 17 25
Merge Sort Algorithm
Merge Sort is one of the most popular sorting algorithms that is based on the principle of
Divide and Conquer Algorithm.
Here, a problem is divided into multiple sub-problems. Each sub-problem is solved
individually. Finally, sub-problems are combined to form the final solution.
Divide
If q is the half-way point between p and r, then we can split the subarray A[p..r] into two
arrays A[p..q] and A[q+1, r].
Conquer
In the conquer step, we try to sort both the subarrays A[p..q] and A[q+1, r]. If we haven't
yet reached the base case, we again divide both these subarrays and try to sort them.
Combine
When the conquer step reaches the base step and we get two sorted subarrays A[p..q] and
A[q+1, r] for array A[p..r], we combine the results by creating a sorted array A[p..r]
from two sorted subarrays A[p..q] and A[q+1, r].