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

Week4 Recursion

Uploaded by

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

Week4 Recursion

Uploaded by

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

CMP2003

Data Structures & Algorithms


Lecture Notes 4
Recursion
Recursion

• A recursive algorithm is one that solves a problem


by solving one or more smaller instances of the
same problem.

• A recursive algorithm is implemented in the form


of a recursive function.

• A recursive function is one that calls itself.


Recursion
Key Points

• Self-Referential: Recursion involves a function


that, during its execution, calls itself either
directly or indirectly.

• Termination Condition: Every recursive function


must have a termination condition, also known
as the "base case," which defines when the
recursion should stop.
Recursive Function
A function which calls itself (see Sedgewick Program 5.1)

int factorial ( int n ) {


if ( n == 0) // base case
return 1;
else //
general/recursive case
return n * factorial ( n – 1);
}
Recursion vs. Iteration: Computing n!
The factorial of a positive integer n, denoted as n!, is defined as the
product of the integers from 1 to n. For example, 4! = 4·3·2·1 = 24.
▪ Iterative Solution

▪ Recursive Solution
Recursion in Action: factorial(n)

Base case arrived


factorial (5) = 5 x factorial Some concept from
(4) elementary maths:
= 5 x (4 x factorial (3))
Solve the inner-
= 5 x (4 x (3 x factorial (2))) most bracket, first,
= 5 x (4 x (3 x (2 x factorial (1)))) and then go
outward
= 5 x (4 x (3 x (2 x (1 x factorial (0)))))
= 5 x (4 x (3 x (2 x (1 x 1))))
= 5 x (4 x (3 x (2 x 1)))
= 5 x (4 x (3 x 2))
= 5 x (4 x 6)
= 5 x 24
= 120

6
Finding a recursive solution
• Each successive recursive call should bring you closer to a
situation in which the answer is known, e.g. in the previous
slide computing (n-1)! helps you get closer to computing n!

• 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. The general case must
converge to the base case!
How to write a recursive function?
• Determine the size factor (e.g. n in factorial(n))
• Determine the base case(s)
– the one for which you know the answer (e.g. 0! = 1)
• Determine the general case(s)
– the one where the problem is expressed as a smaller
version of itself (must converge to base case)
• Verify the algorithm
Linear and Binary Recursion
• The simplest form of recursion is linear recursion, where a method is
defined so that it makes at most one recursive call each time it is
invoked.
Example: factorial(n) calls factorial(n-1)

• Binary recursion occurs whenever there are two recursive calls for
each non-base case.
Example: fibonacci(n) calls fibonacci(n-1) and fibonacci(n-2)

Questions:
• To calculate factorial(5), how many times is the function factorial
invoked (called)?
• To calculate fibonacci(5), how many times is the function fibonacci
invoked?
Linear and Binary Recursion
Question 1:
To calculate factorial(5), how many times is the function factorial
invoked (called)?
int factorial ( int n ) {
if ( n == 0) // base case
return 1;
else // general/recursive case
return n * factorial ( n – 1);
}

factorial(5), factorial(4), factorial(3), factorial(2), factorial(1), factorial(0)


Total calls = 6
Linear and Binary Recursion
Question 2:
To calculate fibonacci(5), how many times is the function fibonacci invoked (called)?

int fibonacci(int n) {
if (n <=1)
return n;
return fibonacci(n-1) + fibonacci(n-2);
}

Total calls = 15
Recursion: run-time analysis
• Time complexity of recursion is proportional to the problem size
– Normally, it is equal to the number of times the function
calls itself
– Check the general case!

• Example 1: Time complexity of factorial(n) is O(n)


• Example 2 (binary recursion): Time complexity of Fibonacci(n) is
O(2n ).
– To be more precise, it is O(1.6180n ). 1.6180 is called the
golden ratio – of course, you do not need to memorize this ☺
12
Summing the Elements of an Array
We can solve this summation problem using linear
recursion by observing that the sum of all n integers
in an array A is:
▪ Equal to A[0], if n = 1, or
▪ The sum of the first n − 1 integers in A plus the last element
linear recursion

int LinearSum(int A[], n){


if (n == 1)
return A[0];
else
return A[n-1] + LinearSum(A, n-1) ;
}
Analyzing Recursive Algorithms using Recursion Traces
Recursion trace for the execution of LinearSum(A,n)
with the input parameters A = [4,3,6,2,5] and n = 5
Linear recursion: Reversing an Array
• Swap 1st and last elements, 2nd and second to last, 3rd and third to last, and so
on
• If an array contains only one element no need to swap (Base case)

i j

5 10 18 30 45 50 60 65 70 80

• Update i and j in such a way that they converge to the base case (i = j)
Linear recursion: Reversing an Array
Reversing an Array

void reverseArray(int A[], i, j){


if (i < j)
{
int temp = A[i];
A[i] = A[j];
A[j] = temp;
reverseArray(A, i+1, j-1) ;
}
// in base case, do nothing
}
16
Recursion and stack management

A quick overview of the stack data structure


• Last in first out (LIFO) data structure
• Push operation adds new element at the top
• Pop operation removes the top element

17
What happens when a function is called?
• The rest of the execution in “caller” is
suspended int a(int w)
{
return w+w;
• An activation record is created on stack, }
containing
int b(int x)
– Return address (in the caller code) {
int z,y=1;
– Current (suspended) status of the caller z = a(x) + y;
return z;
• Control is transferred to the “called” function }

• The called function is executed


• Once the called function finishes its
execution, the activation record is popped of,
and the suspended activity resumes

18
What happens when a recursive function is called?
Except the fact that the calling and called functions have the same name,
there is really no difference between recursive and non-recursive calls.
Tail Recursion

• Tail recursion is a specific form of recursion where


the recursive call is the last operation performed
within the function, just before the function returns
its result.
• This type of recursion can be optimized by compilers
and is sometimes more efficient.

20
Tail Recursion
Key Characteristics:
1. Last Operation: In a tail-recursive function, the recursive
call is the last operation, and its result is immediately
returned. There are no pending operations after the
recursive call.
2. Optimization: Tail recursion can often be optimized by
compilers into an iterative form, eliminating the need to
create new stack frames for each recursive call.

21
Tail Recursion
• Example: Tail-Recursive Factorial
• Consider the tail-recursive implementation of the factorial calculation
function in C++:
Illustration:
int factorial(int n, int result = 1) { •When the factorial function is
called with n and result as
if (n == 0) {
arguments, it multiplies n with
return result; // Base case the current result and
} else { decrements n in each recursive
// Tail-recursive call call.
return factorial(n - 1, n * result); •The base case is reached when
n becomes 0, and at that point,
}
the result is returned as the
} factorial value.

22
Tail Recursion
Advantages of Tail Recursion:
• Efficient memory usage: Tail-recursive functions can be optimized
to avoid creating new stack frames, which can save memory and
prevent stack overflow errors for large inputs.
Notes:
• Not all recursive functions can be easily converted to tail recursion.
The recursive call must be the last operation, and no additional
work should be done after the recursive call for it to be optimized.
• Tail recursion is an important concept, especially when working
with functional programming languages and optimizing recursive
algorithms for performance.

23
Binary Search using Binary Recursion
A is an array, key is the element to be found, LO is initialized as 0,
and HI is initialized as array size - 1
int BinarySearch(int key, int A[], int LO, int HI){
if (LO > HI)then // key does not exist
return -1; Complexity in worst case:
int mid = (LO+HI)/2; O( log2 n)
Best case:
if (key == A[mid]) // base case O(1)
return mid;
else if (key < A[mid]) // recursive case I
BinarySearch(key, A, LO, mid - 1);
else // recursive case
II
BinarySearch(key, A, mid + 1, HI);
}
Efficiency of recursion

• Recursion is not always efficient because:


– It may involve much more operations than necessary (Time
complexity).
– It uses the run-time (system) 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.

25
Recursion: general remarks

Use recursion 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).

26
Examples on Recursion with Linked Lists (1)

Write a recursive function to print all data fields in a Singly Linked List.
void printList(Node* ptr){
if(ptr==NULL) //base case
return;
cout << ptr->data << endl;
printList(ptr->next);
}

27
Examples on Recursion with Linked Lists (2)

Write a recursive function to add all data fields in a Singly Linked List.
int AddNodes(Node* ptr){
if(ptr==NULL) //base case
return 0;
return ptr->data + AddNodes(ptr->next);
}

28
Examples on Recursion with Linked Lists (3)

Write a recursive function to count the nodes in a Singly Linked List.


int Count(Node* ptr){
if(ptr==NULL) //base case
return 0;
return 1 + Count(ptr->next);
}

29
Mathematical Analysis of
Recursive Algorithms

30
Mathematical Analysis of Recursive
Algorithms

31
Mathematical Analysis of Recursive
Algorithms: Towers of Hanoi
• Have n disks of different sizes that can slide onto any of three
pegs
• Initially, all the disks are on the first peg in order of size, the
largest on the bottom and the smallest on top
• The goal is to move all the disks to the third peg, using the
second one as an auxiliary, if necessary
• We can move only one disk at a time, and it is forbidden to
place a larger disk on top of a smaller one

32
Mathematical Analysis of Recursive
Algorithms: Towers of Hanoi

33
Mathematical Analysis of Recursive
Algorithms: Towers of Hanoi
• We first move recursively n − 1 disks from peg
1 to peg 2 (with peg 3 as auxiliary)
• Then move the largest disk directly from peg 1
to peg 3
• Finally, move recursively n − 1 disks from peg 2
to peg 3 (using peg 1 as auxiliary).
• if n = 1, move the single disk directly from the
source peg to the destination peg.

34
Mathematical Analysis of Recursive
Algorithms: Towers of Hanoi

35
Mathematical Analysis of Recursive
Algorithms: Towers of Hanoi

36

You might also like