0% found this document useful (0 votes)
41 views106 pages

A2SV - Recursion Lecture 2024

The document covers the fundamentals of recursion, including prerequisites, basic concepts, and common pitfalls. It explains recursion through real-life examples and provides a structured approach to understanding base cases, recurrence relations, and states. Additionally, it includes sample questions and implementations to reinforce the concepts discussed.

Uploaded by

remidan37
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)
41 views106 pages

A2SV - Recursion Lecture 2024

The document covers the fundamentals of recursion, including prerequisites, basic concepts, and common pitfalls. It explains recursion through real-life examples and provides a structured approach to understanding base cases, recurrence relations, and states. Additionally, it includes sample questions and implementations to reinforce the concepts discussed.

Uploaded by

remidan37
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
You are on page 1/ 106

Fundamentals of

Recursion
Lecture Flow

1. Pre-requisites
2. Problem definitions
3. Basic Concepts
4. Time and Space Complexity of Data Structure
5. Common pitfalls
6. Practice questions and Resources
7. Quote of the day
Pre-requisites

● Understanding iterative program flow


● Functions, local variables and global variables
● Stack data structures
● Willingness to learn
Thinking recursively

Recursion is an important concept in computer science. It is a


foundation for many other algorithms and data structures. However, the
concept of recursion can be tricky to grasp for many beginners.

4
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?

5
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?


6
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
7 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

8
Does this work?
def ask(person):

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

return people

9
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?

10
Does this work?
def ask(person):
if noMorePeople:
return 1

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

return people

11
What happens?

if noMorePeoplen:
return 1 if noMorePeople:
return 1 if noMorePeople:
return 1 if noMorePeople:
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

12
Real Life Example 2

Imagine some one give you a matryoshka and ask you to count the
number dolls, how would you solve the problem?
Brainstorm

14
Brainstorm

15
Brainstorm

16
Brainstorm

17
Brainstorm

18
Brainstorm

Base Case

return 1

19
Brainstorm

return 1+1
Base Case

return 1

20
Brainstorm

return 1+2
return 2
Base Case

return 1

21
Brainstorm

return 1+3

return 3
return 2
Base Case

return 1

22
Brainstorm

return 1+4
return 4

return 3
return 2
Base Case

return 1

23
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

24
Does this work?
def count(matryoshka):

inside = count(matryoshka.child)

return inside + 1

25
What happens?

inside = count(matryoshka.child)
return inside + 1
inside = count(matryoshka.child)
return inside + 1
inside = count(matryoshka.child)
return inside + 1

• • •

We will never get to return because we are calling


the function non stop. What should we do?

26
How about this?
def count(matryoshka):
if matryoshka.empty:
return 1

inside = count(matryoshka.child)

return inside + 1

27
What happens?

if matryoshka.empty:
return 1
if matryoshka.empty:
inside = count(matryoshka.child) return 1
return inside + 1 if matryoshka.empty:
inside = count(matryoshka.child) return 1
return inside + 1
inside = count(matryoshka.child)
return inside + 1

Then now we return the number of people to our


first caller
28
Definitions
Recursion: process in which a function calls itself directly
Basic Concept
The basic concept of recursion is that a problem can be solved intuitively
and much easily if it is represented in one or more smaller versions.

What was the problem in the previous real life example?

What was the subproblem?

?
31
Basic components of recursion

● Base case The condition that signals when the function should stop

and return the final state.

● Recurrence relation Reduces all cases towards base case. The section

where the function calls itself with modified inputs and state

● State An identifier that fully locate which subproblem we are dealing

with currently

32
What were the Base Case, Recursive Relation and The State in the previous
example?
State

def count(matryoshka):
if matryoshka.empty:
return 1 Base Case

inside = count(matryoshka.child)
Recurrence
Relation
return inside + 1

33
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)

34
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

35
State

● Identifies the current subproblem completely


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

36
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

40
Implementation
def isPowerOfFour(self, n: int) -> bool:
# basecases
if n == 1: return True
if n < 1: return False

# function calling itself with change in state


return self.isPowerOfFour(n / 4)
Pair Programming
Fibonacci Number

42
What is the base case and recurrence relation for the above
problem?

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

State
● n

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

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

if n == 0:
return 0

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


How does recursion work?
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

Stack.top -> currently executing


Stack.push -> calling function
Stack.pop -> returning a value

47
Example 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)

49
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)

50
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)

51
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)

52
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)

53
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)

54
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)

55
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)

56
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)

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=3
one = 1
two = fib(1)

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

58
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)

59
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)

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=3
one = 1
two = 1
return one + two

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

61
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)

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=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=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=2
one = fib(0)
two = fib(1)

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

65
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)

66
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)

67
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)

68
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)

69
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)

70
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)

71
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

72
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

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

76
Debugging using the execution tree
● Simulate the code using the
execution tree using pen and
paper by yourself.
● Simulate your code with
recursion simulation sites
● Compare the results

77
Debugging using the call stack and print statements
● Simulate the code
using the call stack N=0 def recursive_fn(state):
return 0 print(“state: “, state)
by yourself using
pen and paper N=2 .
● Use print one = 1 .
two = fib(0)
statements to see if .
your code matches N=3
one = fib(2) print(“result: “, result)
the simulation two = fib(1) return result

N=4
one = fib(3)
two = fib(2)
78
Complexity Analysis
Time Complexity
(Any suggestion?)
There are two parts
● Number of recursive calls
● The complexity of the function

81
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

82
Fib(4)

Facts: 20+1 - 1 4
● Two branching on each
node 3 2

● Height of the tree is the


2 1 1 0
equal to n
● In a full binary tree with n
1 0
height, we have 2n+1 - 1 2
3+1
-1

nodes

83
In general
● Let
○ b = number of branches on each node
○ d = depth of the tree
d+1
● Total number of nodes = b -1

● After only taking the dominant term

(Num of branching) depth of the tree

84
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?

85
In Summary

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


Cost of running function Number of recursive calls

86
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?

87
It uses call stack

88
What is the space complexity of the call
stack?

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

90
Did we forget anything?

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

92
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)

93
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

94
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?


● If the solution to the given problem is achieved by breaking it down into its
subproblems, the recursive approach is a more intuitive and natural way to
solve it.

95
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.

98
Memory Limit Exceeded

is a type of stack overflow error that occurs


when the parameters of our function take too
much space.

99
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

100
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)

101
Wrong recurrence relation

def numOfPeopleInQueue(n: int) -> int:


if n == 1:
return 1

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

102
Using list as state

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


if i >= len(arr):
return 0

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

return pick + notPick

103
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