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

Recursion I(With Code) (1)

The document provides an overview of recursion, including its fundamental concepts, components, and practical applications through examples like counting people in a queue and matryoshka dolls. It discusses prerequisites for understanding recursion, common pitfalls, and the importance of base cases and recurrence relations. Additionally, it covers debugging techniques and the execution tree to visualize recursive calls.

Uploaded by

luuluna721
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)
2 views

Recursion I(With Code) (1)

The document provides an overview of recursion, including its fundamental concepts, components, and practical applications through examples like counting people in a queue and matryoshka dolls. It discusses prerequisites for understanding recursion, common pitfalls, and the importance of base cases and recurrence relations. Additionally, it covers debugging techniques and the execution tree to visualize recursive calls.

Uploaded by

luuluna721
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/ 142

Fundamentals of

Recursion

1
Lecture Flow

1. Prerequisites
2. Motivation Problem
3. Basic Concepts
4. Time and Space Complexity
5. Common Pitfalls
6. Practice Questions and Resources
7. Quote of the Day

2
Prerequisites

● Understanding iterative program flow


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

3
Motivation Problem

Imagine you are in a long queue and the reception asks you how many
people there are in the line.

Each person can only talk to the person behind and infront of them.

How do you count the number of people in the queue?

4
Brainstorm
● Let’s assume each person in the queue
○ Can increment numbers by 1
○ Can ask other people

● What if I asked the person behind me “How many people are there behind
you ?” and whatever their response is, I can add myself and do a plus 1 and
inform the reception.

● And what if the person I asked, asks the person behind him “How many
people are there behind you ?” and whatever he gets, he adds himself and
do plus 1 and informs me.


5
1
How many
people are
there?

6
How many
people are
there?

2
How many
people are
there?

7
How many
people are
there?
3
How many How many
people are people are
there? there?

8
How many
people are
there?
4
How many How many
people are people are How many
there? there? people are
there?

9
How many
people are
there?
5
How many How many
people are people are
there? No one is
there?
behind me.

10
How many
people are
there?
5
How many How many
people are people are
there? No one is
there?
behind me.

Stopping
case

11
How many
people are
there?

How many How many


people are people are
there? there?

Only Me

12
How many
people are
there?
7
How many There is one person
people are behind me, So...
there? including me it will
be Two

13
How many
people are
there?

Two

14
How many
people are
there?
9
There are two people
behind me, So...
including me it will be
Three

15
How many
people are
there?

10

Three

16
11
There are
three people

17
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

18
Does this work?

def ask(person):

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

return people

19
What happens?
behind =
ask(person + 1) behind =
people = behind + ask(person + 1)
behind = ask(person
1 people = behind +
+ 1)
1
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?
20
Does this work?
def ask(person):
if noMorePeople:
return 1

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

return people

21
What happens?

if noMorePeople:
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

22
Real Life Example 2

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

23
Brainstorm

24
Brainstorm

25
Brainstorm

26
Brainstorm

27
Brainstorm

28
Brainstorm

Base Case

return 1

29
Brainstorm

return 1+1
Base Case

return 1

30
Brainstorm

return 1+2
return 2
Base Case

return 1

31
Brainstorm

return 1+3

return 3
return 2
Base Case

return 1

32
Brainstorm

return 1+4
return 4

return 3
return 2
Base Case

return 1

33
Brainstorm

return 5
return 4

return 3
return 2
Base Case

return 1

34
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

35
Does this work?

def count(matryoshka):

inside = count(matryoshka.child)

return inside + 1

36
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?
37
How about this?

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

inside = count(matryoshka.child)

return inside + 1

38
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

39
Definitions

40
Recursion: process in which a function calls itself directly

41
Basic Concept
A problem can be solved intuitively and much more easily if it is
represented as an operation of it’s smaller versions.

What was the problem in the previous real life example?

What was the subproblem?

What is the operation we perform ?

42
Basic Components of Recursion

● State

● Base case

● Recurrence relation

43
State

● The set of parameters that fully and uniquely describe a

particular subproblem completely.

44
Base Case

● Is a known and the simplest state of the problem.


● Is a terminating scenario and can no longer be solved
recursively.
● Initiates the process of returning to the original call
function (or problem).

45
Recurrence Relation

● Defines the relation between subsequent states.


● It aggregates the result from recursive calls and returns the answer
we have so far to the caller.
● This is a section where the function calls itself with a modified state.
● Changes in state should lead towards base cases. (i.e should be a
breakdown of the current problem into a smaller problem).
46
What is the state, the base case and recurrence relation for
the previous problem?

47
?

def count(matryoshka):
if matryoshka.empty:
?
return 1

prev = count(matryoshka.child) ?
curr = prev + 1

return curr

48
State

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

prev = count(matryoshka.child) Recurrence


curr = prev + 1 Relation

return curr

49
Practice

Factorial Trailing Zeros

50
What is the state, the base case and recurrence relation for
the above problem?

51
Base Case
● n == 1

State
● n

Recurrence Relation
● f(n)=n*f(n-1)

52
How does this work?

53
factorial(n) factorial of n gets called

54
factorial(n)

calls factorial(n-1)
factorial(n-1)

55
factorial(n)

factorial(n-1)

calls factorial(n-2)
factorial(n-2)

56
factorial(n)

factorial(n-1)

factorial(n-2)

57
factorial(n)

factorial(n-1)

factorial(n-2)

..

58
factorial(n)

factorial(n-1)

factorial(n-2)

..
.

59
factorial(n)

factorial(n-1)

factorial(n-2)

..
.
calls factorial(2)
factorial(2)

60
factorial(n)

factorial(n-1)

factorial(n-2)

..
.
factorial(2)

calls factorial(1)
factorial(1)
61
factorial(n)

factorial(n-1)

factorial(n-2)

..
.
factorial(2)

factorial(1) {basecase}
62
factorial(n)

factorial(n-1)

factorial(n-2)

..
.
factorial(2)
{returns 1}

factorial(1)
63
factorial(n)

factorial(n-1)

factorial(n-2)

..
. {returns 2 which is (2) * factorial(1)}

factorial(2)
{returns 1}

factorial(1)
64
factorial(n)

factorial(n-1)

factorial(n-2)

..
. . {returns 2 which is (2) * factorial(1)}

factorial(2)
{returns 1}

factorial(1)
65
factorial(n)

factorial(n-1)

factorial(n-2)

.. .
. . {returns 2 which is (2) * factorial(1)}

factorial(2)
{returns 1}

factorial(1)
66
factorial(n)

factorial(n-1)

factorial(n-2)

. .. .
. . {returns 2 which is (2) * factorial(1)}

factorial(2)
{returns 1}

factorial(1)
67
factorial(n)

factorial(n-1)
{returns factorial(n-2) which is (n-2) * factorial(n-3)}

factorial(n-2)

. .. .
. . {returns 2 which is (2) * factorial(1)}

factorial(2)
{returns 1}

factorial(1)
68
factorial(n)
{returns factorial(n-1) which is (n-1) * factorial(n-2)}

factorial(n-1)
{returns factorial(n-2) which is (n-2) * factorial(n-3)}

factorial(n-2)

. .. .
. . {returns 2 which is (2) * factorial(1)}

factorial(2)
{returns 1}

factorial(1)
69
{returns factorial(n) which is (n) * factorial(n-1)}
factorial(n)
{returns factorial(n-1) which is (n-1) * factorial(n-2)}

factorial(n-1)
{returns factorial(n-2) which is (n-2) * factorial(n-3)}

factorial(n-2)

. .. .
. . {returns 2 which is (2) * factorial(1)}

factorial(2)
{returns 1}

factorial(1)
70
Pair Programming

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

72
Dividing a power of four by four

64 / 16 / 4 / 1

Dividing a random number by four

28 / 7 / 1.75 / 0.44

73
Implementation

def isPowerOfFour(self, n: int) -> bool:


# base cases
if n == 1: return True
if n < 1: return False

# function calling itself with change in state


return self.isPowerOfFour(n / 4) # float division

74
Execution Tree
● Is a tree-like representation of all possible execution
paths of a program.

● Each node in the tree represents a state of the


program, and each edge represents a transition from
one state to another.

75
Using Execution Tree

76
Fibonacci Sequence

77
Fibonacci Sequence

● Series of numbers where each number is the sum of


the two preceding numbers

● Let’s use recursion to implement this.

78
Step by Step Simulation
Go to this LINK

79
Depth of the
execution tree
Num of nodes
approximately
: 2n+1 - 1

80
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

81
How does recursion work?

82
How does the recursion work?
● The system uses some ordering to execute the calls
● The ordering is done based on Last In First Out order
● It uses stack
stack.top() -> currently executing
stack.push() -> calling function
stack.pop() -> returning a value

83
Example Using Call Stack

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

108
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

109
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

110
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)
111
Complexity Analysis

112
Time Complexity
(Any suggestion?)

113
There are two parts

● Number of recursive calls

● The complexity of the function

114
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

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

3 2

Fib(4)
2 1 1 0

23+1 - 1 1 0

116
In general
● Let
○ b = number of branches on each noden (number of calls each function makes)
○ 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

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

118
In Summary

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


Cost of running function Number of recursive calls

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

120
It uses call stack

121
What is the space complexity of the call
stack?

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

123
Did we forget anything?

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

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

126
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

127
Draw The Execution Tree

Pow(x,n)

128
Draw The Execution Tree
(with two branches)

Pow(x,n)

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

130
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

131
Common Pitfalls

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

133
Memory Limit Exceeded

is a type of stack overflow error


that occurs when the parameters of
our function take too much space.

134
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

135
A function with no base case

def ask(person):

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

return people

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

138
Wrong recurrence relation

def numOfPeopleInQueue(n: int) -> int:


if n == 1:
return 1

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

139
Practice Questions
Power of Two
Power of three
Pascal’s Triangle II
Remove Linked List Elements
Remove Nodes From Linked List
Merge Two Sorted List
Decode String
Reverse Linked List
Count Good Numbers
Flatten a Multilevel Doubly Linked List
Predict the Winner

140
Resources
● Leetcode Recursion I Card
● Leetcode Recursion II Card
● Recurrence Relations

141
Quote
“In order to understand recursion, one must first
understand recursion.”

Unknown

142

You might also like