0% found this document useful (0 votes)
9 views105 pages

Recursion Fundamentals and Examples

The document outlines the fundamentals of recursion, including prerequisites, problem definitions, and the importance of base cases and recurrence relations. It provides examples and steps to solve recursive problems, emphasizing the breakdown of problems into smaller subproblems and the use of a call stack. Additionally, it discusses common pitfalls and offers practice questions to reinforce understanding.

Uploaded by

Kaleb Mulatu
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)
9 views105 pages

Recursion Fundamentals and Examples

The document outlines the fundamentals of recursion, including prerequisites, problem definitions, and the importance of base cases and recurrence relations. It provides examples and steps to solve recursive problems, emphasizing the breakdown of problems into smaller subproblems and the use of a call stack. Additionally, it discusses common pitfalls and offers practice questions to reinforce understanding.

Uploaded by

Kaleb Mulatu
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

Fundamentals of

Recursion
Lecture Flow

1) Pre-requisites
2) Problem definitions
3) Time and Space Complexity of Data Structure
4) Check point
5) Recognizing in Questions
6) Things to Pay Attention(common pitfalls)
7) Practice questions
8) Resources
9) Quote of the day
Pre-requisites
● Understanding iterative program flow
● Functions, local variables and global variables
● Stack data structures
Real Life Example

Imagine you are in a long queue and the reception asks you how many
people there are in the line. How do you count the number of people in
the queue?
Brainstorm
● Let’s assume each person in the queue
○ Can increment numbers by 1
○ Can request other people
● What if I asked the person behind me “how many people there are” and
whatever their response is, I can do plus 1 and inform the reception? And
● What if the person I asked, asks the person behind them “how many people
there are” and whatever they get, they do plus 1 and inform me?


5
1 Asking and Aggregating steps
How many
people are 2
How many 4
there? 3
How many people behind
people you including 5
reception behind you yourself? Only Me
Let me
including
check…
yourself?
2 + 1= 3
7

6
There
are 2
Third
people
Second person
First person
person

Stopping
case
Can we simulate
this with code?
● Since we are doing
the same thing again
and again let’s use
one function to do
the task

7
Does this work?
def ask(person):

behind = ask(person + 1)
people = behind + 1

return people

8
What happens?

behind = ask(person + 1)
people = behind + 1 behind = ask(person + 1)
people = behind + 1 behind = ask(person + 1)
people = behind + 1 behind = ask(person + 1)
return people
people = behind + 1
return people • • •
return people
return people

We will never get to return because we are calling


the function non stop. What should we do?

9
Does this work?
def ask(person):
if noMorePerson:
return 1

behind = ask(person + 1)
people = behind + 1

return people

10
What happens?

if noMorePerson:
return 1 if noMorePerson:
return 1 if noMorePerson:
return 1 if noMorePerson:
behind = ask(person + 1)
return 1
people = behind + 1 behind = ask(person + 1)
people = behind + 1 behind = ask(person + 1)
people = behind + 1 behind = ask(person + 1)
return people
people = behind + 1
return people
return people
return people

Then now we return the number of people to our


first caller

11
Definitions
Recursion: process in which a function calls itself directly
Basic Concept

The basic concept of recursion is that a problem can be solved much


easily and in lesser time if it is represented in one or more smaller
versions.

What was the problem in the previous real life example?

What was the subproblem?

14
Fundamental rules of recursion

● Base case is a terminating condition and it doesn’t use any recursive

calls to get answers

● Recurrence relation that reduces all cases towards base case

● State is an identifier that fully locate which subproblem we are dealing

with currently

15
Recurrence Relation

How many
How many people behind
people you including
behind you yourself? Only Me
Let me
including
check…
yourself?
2 + 1= 3

How many
people are
there? There
are Two
State:
people
First State: Third
Caller: State: Second person
reception First person
person
Base Case
state

def ask(person):
if noMorePerson:
return 1 Base Case

behind = ask(person + 1)
people = behind + 1 Recurrence
Relation
return people

17
State

● Identifies the current subproblem completely


● Helps locate which subproblem we are dealing with currently
● Changes in state should lead towards base cases

18
Base case

● are known and simplest cases of the problem


● is a terminating scenario
● initiate the process of returning to the original call function (or
problem)

19
Recurrence Relation

● should be a breakdown of the current problem into smaller problems


● Is a set of cases reduces the current case towards base case
● It aggregates the result from recursive calls and returns the answer
we have so far to caller

20
Sample Question
Solution

Q1. What happens if we continue to divide a number which is a power of four


by four?
● The final result will be 1

Q2. What if the number is not power of four?


● Their number will be below 1 eventually

From the above questions we can see that we have two base cases and the
state is the number that is divided by four every time we have recursive call.
Dividing a power of four by four

64 / 16 / 4 / 1

Dividing a random number by four

28 / 7 / 1.75 / 0.44

24
Pair Programming
Power of four

25
Steps to solve a recursive problem
1. Break down the problem into smaller subproblems
2. Call the function to solve the smaller subproblems
3. Combine the result from the smaller subproblems to get the answer for the
bigger sub-problem

26
Let’s solve this problem using the three steps

27
1. Break down the problem into smaller problems

Power of three is a number made up of multiple 3 products


❏ 3•3•3•…•3

check first whether the number is divisible by 3

and then check whether the remaining factor is divisible by 3 recursively


❏ 3 • (3 • 3 • … • 3)
❏ 3 • 3 • (3 • … • 3)
❏ 3 • 3 • 3 • ( ...• 3)

If all checks are divisible by three, then the number must be power of three

28
Is 27 divisible by three and
reduce Combine:
True
Is 9 = (27/3) divisible by three and
Combine:
reduce
True
Is 3 = (9/3) divisible by three and
reduce Combine:
Is 1 = (3/3) True

If 1 return True
Base Case
If 0 return False

29
2. Call the function to solve the smaller problems

Given that the current number is divisible by three


Check whether the rest of the number is divisible by three after dividing by
three

n % 3 == 0 and isPowerOfThree(n//3)

30
3. Combine the result from the smaller problems

The subproblems return whether the number was divisible by three

According to the subproblems, we can decide whether the number is power of


three or not

If the number is divisible by three and the number divided by three is recursively
divisible by three, the number is power of three

31
Pair Programming
(swap between driver and passenger)
Power of three

32
Steps to follow to write recursive function

33
There are three things that needs to be
figured out for every recursive function

What are the three things?

34
The base case, State &
Recurrence relation

35
Base Case State Recurrence
Relation
● An answer that can ● A value that represent ● A relation between the
be computed without which subproblem we result of current
using recursive calls are currently tackling problem and its
subproblems
● It is the smallest and ● The subproblems
simplest case that is should reduce the
known current problem
eventually leading to
base case

36
Take out a notebook and write the base case and recurrence
relation for the problem below

37
Base Case
● If n == 1, return 1
● If n == 0, return 0

State
● n

Recurrence Relation
● f(n)=f(n-1)+f(n-2)

38
Pair Programming
(swap between driver and passenger)
Fibonacci Number

39
How does recursion work?
Using Call Stack
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two

N=4
one = fib(3)
two = fib(2)

42
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two

N=3
one = fib(2)
two = fib(1)

N=4
one = fib(3)
two = fib(2)

43
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1) N=2


two = fib(n-2) one = fib(1)
two = fib(0)
return one + two

N=3
one = fib(2)
two = fib(1)

N=4
one = fib(3)
two = fib(2)

44
def fib(n: int) -> int:
if n == 1:
return 1
N=1
return 1
if n == 0:
return 0

one = fib(n-1) N=2


two = fib(n-2) one = fib(1)
two = fib(0)
return one + two

N=3
one = fib(2)
two = fib(1)

N=4
one = fib(3)
two = fib(2)

45
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1) N=2


two = fib(n-2) one = 1
two = fib(0)
return one + two

N=3
one = fib(2)
two = fib(1)

N=4
one = fib(3)
two = fib(2)

46
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1) N=2


two = fib(n-2) one = 1
two = fib(0)
return one + two

N=3
one = fib(2)
two = fib(1)

N=4
one = fib(3)
two = fib(2)

47
def fib(n: int) -> int:
if n == 1:
return 1
N=0
return 0
if n == 0:
return 0

one = fib(n-1) N=2


two = fib(n-2) one = 1
two = fib(0)
return one + two

N=3
one = fib(2)
two = fib(1)

N=4
one = fib(3)
two = fib(2)

48
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1) N=2


two = fib(n-2) one = 1
two = 0
return one + two

N=3
one = fib(2)
two = fib(1)

N=4
one = fib(3)
two = fib(2)

49
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0
N=2
one = fib(n-1) one = 1
two = fib(n-2) two = 0
return one + two
return one + two

N=3
one = fib(2)
two = fib(1)

N=4
one = fib(3)
two = fib(2)

50
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two

N=3
one = 1
two = fib(1)

N=4
one = fib(3)
two = fib(2)

51
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two

N=3
one = 1
two = fib(1)

N=4
one = fib(3)
two = fib(2)

52
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1) N=1


two = fib(n-2) return 1

return one + two

N=3
one = 1
two = fib(1)

N=4
one = fib(3)
two = fib(2)

53
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two


N=3
one = 1
two = 1
return one + two

N=4
one = fib(3)
two = fib(2)

54
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two


N=3
one = 1
two = 1
return one + two

N=4
one = fib(3)
two = fib(2)

55
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two

N=4
one = 2
two = fib(2)

56
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two

N=4
one = 2
two = fib(2)

57
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two


N=2
one = fib(0)
two = fib(1)

N=4
one = 2
two = fib(2)

58
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1) N=0


two = fib(n-2) return 0

return one + two


N=2
one = fib(0)
two = fib(1)

N=4
one = 2
two = fib(2)

59
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two


N=2
one = 0
two = fib(1)

N=4
one = 2
two = fib(2)

60
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two


N=2
one = 0
two = fib(1)

N=4
one = 2
two = fib(2)

61
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1) N=1


two = fib(n-2) return 1

return one + two


N=2
one = 0
two = fib(1)

N=4
one = 2
two = fib(2)

62
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two


N=2
one = 0
two = 1

N=4
one = 2
two = fib(2)

63
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two


N=2
one = 0
two = 1
return one + two

N=4
one = 2
two = fib(2)

64
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two

N=4
one = 2
two = 1

65
def fib(n: int) -> int:
if n == 1:
return 1

if n == 0:
return 0

one = fib(n-1)
two = fib(n-2)

return one + two

N=4
one = 2
two = 1
return 3

66
How does the recursion work?

● The system uses some ordering to execute the calls


● The ordering is done based on Last In First Out (depth first) order
○ It uses stack

[Link] -> currently executing


[Link] -> calling function
[Link] -> returning a value

67
Using Execution Tree
Step by Step Simulation
Go to this LINK
Depth of the
execution tree
Num of nodes
approximately
: 2n - 1

70
Reflection
● Why is the num of nodes for a full tree 2n - 1 ?
● Why is the depth of the execution tree n?
● In what order were we traversing the tree?

71
Complexity Analysis
Time Complexity
(Any suggestion?)
There are two parts
● Number of recursive calls
● The complexity of the function

74
How do we find the number of calls?
● Draw the execution tree
● Execution tree is a tree that describes the
flow of execution for the recursive function
● A node represents a call
● The total number of nodes is your final
answer

75
Fib(4)

Facts: 20 4
● Two branching on each
node
21 3 2
● Height of the tree is the
equal to n
22 2 1 1 0
● In a full binary tree with n
height, we have 2n - 1 nodes
23 1 0

20 + 21 + … + 2n = 2n - 1
76
In general
● How many branching?
● What is the depth?

(Num of branching) depth of the tree

77
What is the complexity of the function?
● This is plain time and space complexity
analysis of the inner working of the function
● Are we doing any costly operations in the
function?

78
In Summary

O(function) • O((Num of branching) depth of the tree )


Cost of running function Number of recursive calls

79
Space Complexity
Before we discuss the space complexity, let’s consider these questions
● How does the computer run recursion function?
● How does it know when to return and combine?

80
It uses call stack

81
What is the space complexity of the call
stack?

82
Maximum size of the stack which is maximum
depth of the execution tree

83
Did we forget anything?

84
What about the states and space complexity
of the function?

85
In Summary
N=0
return 0

State Size + function N=2


complexity one = 1
two = fib(0)
depth
[O(states’ size) + O(space complexity of function) ] •
O(maximum depth of the execution tree ) N=3
one = fib(2)
two = fib(1)

N=4
one = fib(3)
two = fib(2)

86
Reflection
● Can you draw execution tree for the previous problems you solved?
● In the simulation, what was the call stack?
● What is the max size of the call stack?
○ Depth of the execution tree
● In what order is the execution tree traversed?
○ Left Node first
○ Right Node second
○ Parent Node last
Checklist (Ask your instructor, if anything is missing)
❏ You have to be very comfortable with writing out the base case and
recurrence relation of recursive function
❏ You have to be very comfortable with drawing execution tree of a recursion
function
❏ Understanding the execution order using the execution tree
❏ Comfortably analyze the time and space complexity of a recursive function

88
Recursion versus Iteration

Anything that can be done recursively can be done iteratively


● Iteration is faster and more space-efficient than recursion.
● Recursion has function call overhead
● Iterative programs are easy to debug

So why do we even need recursion?


● It's easier to code a recursive approach for a given problem.
● Recursive codes are smaller and easier to understand.

89
Debugging
Debugging using the call stack
N=0
return 0
● Simulate the code using the
call stack
● Let’s go revisit the simulation N=2
one = 1
from the example on slide 48 two = fib(0)

N=3
one = fib(2)
two = fib(1)

N=4
one = fib(3)
two = fib(2)

91
Debugging using the execution tree
● Simulate the code using the
execution tree
● Let’s go revisit the simulation
from the example on slide 76

92
Debugging using print statements

● First notice that similar problems have similar solutions


○ recursive_function(n) == recursive_function(m) is always equal
given that n == m
4

3 2

2 1 1 0

1 0
93
Debugging using print statements:
def recursive_fn(state):
print(“state: “, state)

.
.
.

print(“result: “, result)
return result

94
Recognizing in Questions
● Look for patterns in the question that suggest that the same
operation is being applied to smaller versions of the same
problem.
● Look for base cases or stopping conditions that define when
the recursion should end.
● Searching problems with branching conditions
Things to pay attention
Stack Overflow ?
Occurs when the program uses up the memory assigned to it in the call stack

In the case of recursion we will mainly deal with two types:


- Memory Limit Exceeded
- Maximum Recursion Depth Exceeded.

97
Memory Limit Exceeded

is a type of stack overflow error that occurs


when the parameters of our function take too
much space.

98
Maximum Recursion Depth Exceeded

is a type of stack overflow error that occurs when


the number of recursive calls being stored in the
call stack is greater than allowed.

Different programming languages have different


maximum recursion Depth sizes:
- Python => 1000
- Javascript => 10,000
- Java => around 10,000
- C++ =>No Limit

99
A function with missing base case

def fib(n: int) -> int:


if n == 1:
return 1

What happens when n == 0?

return fib(n-1) +
fib(n-2)

100
Wrong recurrence relation

def numOfPeopleInQueue(n: int) -> int:


if n == 1:
return 1

prev = numOfPeopleInQueue(n-1) + 1
return prev + 1

We are doing + 1 unnecessarily

101
Using list as state

def divideInBuckets(i, picked: List) -> int:


if i >= len(arr):
return 0

notPick = divideInBuckets(i+1,picked)
[Link](arr[i])
pick = divideInBuckets(i+1,picked) Passed by
reference

return pick + notPick

102
Practice Questions
Reverse String
Count Good Numbers
Pascal’s Triangle II
Merge Two Sorted List
Decode String
Predict the Winner
Power of three
Power of four
Resources
● Leetcode Recursion I Card
● Leetcode Recursion II Card
Quote
“In order to understand recursion, one must first
understand recursion.”

Unknown

You might also like