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

Recursion_Advanced Python

The lecture covers recursion in Python, explaining its definition, how it differs from iteration, and its applications in problem-solving techniques like Fibonacci, factorial, and exponential functions. It discusses the importance of base and recursive cases, the mechanics of function calls, and optimization strategies such as memoization. Additionally, it compares recursion and iteration in terms of performance and readability, highlighting when to use each approach.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views

Recursion_Advanced Python

The lecture covers recursion in Python, explaining its definition, how it differs from iteration, and its applications in problem-solving techniques like Fibonacci, factorial, and exponential functions. It discusses the importance of base and recursive cases, the mechanics of function calls, and optimization strategies such as memoization. Additionally, it compares recursion and iteration in terms of performance and readability, highlighting when to use each approach.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 61

Lecture 4: Recursion in Python

Larissa C. Shimomura
Outline

• Recursion
• Recursion vs. Iteration
• Search and Sorting
• Divide and Conquer algorithms
What is Recursion?

• A function is recursive if it calls itself!

• Problem solving technique in which problems are solved by breaking the


problem into smaller problems of the same form.
What is Recursion?

• Example 1: Fibonacci - Sequence in which each number is the sum of the two
preceeding ones

n=0,1
0, 1

0+1=1 n=2
n=6

1+1=2 n=3
3+ 5 = 8

1+2=3 2+3=5
n=4
n=5
Problem solving using recursion

• Key questions to problem solving • In Fibonacci:


in recursion:
1. Smaller problem: Sum of the two
1. How to break into smaller previous numbers
problems?
2. Call in a determined sequence
2. How to reuse the results of the the function of summing the two
smaller problems to get to the previous numbers.
nal result?
Recursion!!!
In practice, when implementing
recursion it means that the program/
function will call itself!
fi
Problem solving using recursion

• Fibonacci - Sequence in which each number is the sum of the two preceeding
ones

• Call sequence:
n=0,1

0, 1

0+1=1 n=2
n=6

1+1=2 n=3
3+ 5 = 8

1+2=3 2+3=5
n=4
n=5
Problem solving using recursion

• In Fibonacci:
1. Smaller problem: Sum of the two
previous numbers

2. Call in a determined sequence


the function of summing the two
previous numbers.

Recursion!!!
In practice, when implementing
recursion it means that the program/
function will call itself!
Must haves for Recursion

1. Case for all valid inputs

2. Base case that makes no


recursive calls
Base Case
3. Recursive Case calls to a simpler
case that will eventually move to
the base case Recursive Case

Without the base case and a properly written recursive case, the function will be called in nitely!

fi
What is happening under the hood?
How does it move to simpler cases and eventually to the base case?
• It’s a function! Each previous call waits for the next call to nish.
• First, it makes all the calls until it reaches the base case
• Each function call is added to the call stack
bonacci(5)

bonacci(4) bonacci(3)

bonacci(3) bonacci(2) bonacci(2) bonacci(1)

bonacci(2) bonacci(1) bonacci(1) bonacci(0) bonacci(1) bonacci(0)

bonacci(1) bonacci(0)
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
What is happening under the hood?
How does it move to simpler cases and eventually to the base case?
• It’s a function! Each previous call waits for the next call to nish.
• Base case can be solved without extra function calls - Function nishes, answer is returned to previous call!
• Whenever a function nishes it gets removed from the call stack
bonacci(5)

bonacci(4) bonacci(3)

bonacci(3) bonacci(2) bonacci(2) bonacci(1)

bonacci(2) bonacci(1) bonacci(1) bonacci(0) bonacci(1) bonacci(0)

bonacci(1)
Base Case!
bonacci(0)
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
What is happening under the hood?
How does it move to simpler cases and eventually to the base case?
• It’s a function! Each previous call waits for the next call to nish.
• Previous call uses the answer of the nished function!
3+2=5
Recursive Cases
bonacci(5)
2+1=3 1+1=2
bonacci(4) bonacci(3)
1+1=2 0+1=1 0+1=1
bonacci(3) bonacci(2) bonacci(2) bonacci(1)

0+1=1
bonacci(2) bonacci(1) bonacci(1) bonacci(0) bonacci(1) bonacci(0)

bonacci(1)
Base Case!
bonacci(0)
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
Examples - Factorial and Exponential
See Recursion-pt1.ipynb
Example 2 - Factorial Example 3 - Exponential

• Base Case: 0! =1 • Take (n,x) as input in which n is a number


n
and x is the exponent and return x
• Recursive Case: n! = (n)* (n-1) * (n-2)
*….1 = (n)*(n-1)! • Base Case: (n,0) = 1 or (n,1) = n
• Observe that recursive case will • Recursive Case: n * n (x−1)
eventually lead to 0!
What about this function?

• What is this function doing?


• Answer: Printing
What about this function? (Pt. 2)

• Di erent from before: Recursive rst and then print


• What is this function doing? • Answer: Reverse Printing
ff
fi
To implement recursion….

• Think in simple steps! Understand the problem!


• What is the case that is straightforward to solve? - That’s your base case!!
• How do you use the previous results to achieve the next result? - That’s your
recursive step!

• Start from simpler cases and move eventually to more di cult ones
• Since the recursive steps always calls a previous step, it can help to think
about the problem backwards.

ffi
Recursion vs. Iteration
Can we rewrite recursive programs to iterative programs? - Yes!!

• Recursive programs can be transformed into iterative programs by making


use of control constructs (loops, condition statements, etc.)
Iterative Version

Recursive Version
Recursion vs. Iteration
Can we rewrite recursive programs to iterative programs? - Yes!!

• Recursive programs can be transformed into iterative programs by making


use of control constructs (loops, condition statements, etc.)
Iterative Version

Recursive Version
Recursion vs. Iteration
- Iteration can be simulated by recursion

- Every recursive function can be written to an equivalent function without


recursion - DIFFICULT!
When to use recursion or iteration?
What to take into consideration when choosing?

Code Readability and


Performance
Implementation

Iteration - Speed! Recursion - ease of use!


When to use recursion or iteration?
What to take into consideration when choosing?

Code Readability and


Performance
Implementation

Recursive implementations Recursive programs that are correctly


increase the call stack and have to implemented can be more elegant
wait for the function calls to nish and easier to read/understand

Which one to use depends on your use case and problem you are dealing with!
fi
Recursion vs. Iteration
Performance - Execution Time
Recursive Version Iterative Version
Stack Overflow - Recursion depth in Python

• Every function call is pushed to a stack • It is possible to change the limit by


using the function
• If the recursion is too deep it can cause sys.setrecursionlimit(n).
an error

• In Python there is a limit of recursion calls • Be careful! It can a ect the


performance of your program
ff
When to use recursion or iteration?
Problems that are commonly implemented using recursion (next part of the lecture)

• Tree traversals
• Graph searches: Depth- rst search and Path searches
• Sorting algorithms
• Merge Sort
• Quick Sort

Which one to use depends on your use case and the problem you are dealing with!
fi
Memoization

• Going back to our bonacci example:


3+2=5
Recursive Cases
bonacci(5)
2+1=3 1+1=2
bonacci(4) bonacci(3)
1+1=2 0+1=1 0+1=1
bonacci(3) bonacci(2) bonacci(2) bonacci(1)

0+1=1
bonacci(2) bonacci(1) bonacci(1) bonacci(0) bonacci(1) bonacci(0)

bonacci(1)
Base Case!
bonacci(0)
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
Memoization
• It is exponential - each call, makes 2 other calls
• Many calls with the same parameters 3+2=5

bonacci(5)
2+1=3 1+1=2
bonacci(4) bonacci(3)
1+1=2 0+1=1 0+1=1
bonacci(3) bonacci(2) bonacci(2) bonacci(1)

0+1=1
bonacci(2) bonacci(1) bonacci(1) bonacci(0) bonacci(1) bonacci(0)

bonacci(1) bonacci(0)
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
Memoization

• Write an optimized version of the following implementation of Fibonacci using


recursion. What should we do?

Hint: Think about the number of calls to the same cases


Memoization

• Write an optimized version of the following implementation of Fibonacci using


recursion. What should we do? - Reuse calculated values!!!

• Maintain a dictionary on values that are already known/calculated


Memoization - @cache

• This technique is very well-known, so python implements it by using the


decorator @cache (from functools)
Additional notes

• Optimization strategies: Memoization and Tail Recursion


• Memoization: Store and reuse results of expensive function calls
• Tail Recursion: Recursive function call as the last operation
• Performance di erence when using or not the strategies
ff
Search

• Linear Search
• Binary Search
Guessing Game (1)
picks a secret number
between 1 and 1024 makes a guess

answers with “correct”


or “incorrect”

The objective is to minimise the number of guesses.


Which algorithm should Player 2 follow?

There is no better strategy for Player 2


than trying all numbers in some ordering
Linear Search

Worst-case: linear number of comparisons


O(n): number of comparisons grows linearly with Big O notation
the size of the input
Simple de nition: How hard an algorithm may have to work to solve a problem
fi
O(1) - Constant time

• An algorithm designed to test whether the rst element of an array is equal to the second

• If the array has 10 elements, this algorithm requires one comparison

• If the array has 1000 elements, it still requires one comparison

• Algorithm is completely independent of the number of elements in the array

fi
Guessing Game (2)
picks a secret number
between 1 and 1024 makes a guess

answers with “correct”,


“higher”, or “lower”.

The objective is to minimise the number of guesses.


Which algorithm should Player 2 follow?

The best strategy is:


guess the middle number.
Why start from the middle?

0 512 1024

0 256 512 1024


If number < 512
Search Space is reduced!
Less numbers to check
Binary Search
List/Array must be sorted!

Can be guessed in log(1024) = 10 guesses

Algorithm
1. Let [x,y] be the interval containing the secret number (for example, [1,1024])
2. Let m be round((x+y) / 2) (for example, 512). Guess m.
3. If m is correct then stop
4. If m is smaller than the secret number, set x equal to m+1 and goto step 1
5. If m is larger than the secret number, set y equal to m-1 and goto step 1
Binary Search Implementation
def binary_search(data, key):
low = 0
high = len(data) - 1
middle = (low + high + 1) // 2
location = -1

while low <= high and location == -1:


if key == data[middle]:
location = middle
elif key < data[middle]:
high = middle - 1
else:
low = middle + 1
middle = (low + high + 1) // 2

return location

O(log(n))
Binary Search

0 512 1024
Low Middle High

0 256 512 1024


Low Middle High
Other notes in the book…

• Introduction to performance of recursion algorithms - Big-O notation


• Linear, Log(N) and Exponential recursive calls
• Chp 11.8
Sorting

• Selection sort
• Insertion sort
• Merge sort
Selection Sort

• First iteration selects the smallest element in the array and swaps it with the rst element

• Second iteration selects the second-smallest item (which is the smallest item of the remaining elements)
and swaps it with the second element

• The algorithm continues until the last iteration selects the second-largest element and swaps it with the
second-to last index, leaving the largest element in the last index

fi
Selection Sort
index1 smallest

smallest
index1

index1


Selection Sort

def selection_sort(data):
for index1 in range(len(data) - 1):
smallest = index1 # first index of remaining array

# loop to find index of smallest element


for index2 in range(index1 + 1, len(data)):
if data[index2] < data[smallest]:
smallest = index2

# swap smallest element into position


data[smallest], data[index1] = data[index1], data[smallest]

O(nˆ2)
Selection Sort

• First iteration: n-1 comparisons


• Second iteration: n-2 comparisons
• … last iteration:1 comparison
• Total: n-1 + n-2 + n-3 + … + 2 + 1 = n(n – 1)/2
• In Big O, smaller terms drop out, and constants are ignored, leaving O(n2)
Insertion Sort

• General idea: Traverse the array and insert


each element at the correct position

• Also a quadratic O(n^2) complexity algorithm

• The outer loop will select the next number

• The inner loop will go through the already


ordered part of the array and insert it in the
correct position

• If you start with a sorted list - there is no need


to insert!
Insertion Sort

def insertion_sort(data):
for next in range(1, len(data)):
insert = data[next] # value to insert
move_item = next # location to place element

# search for place to put current element


while move_item > 0 and data[move_item - 1] > insert:
# shift element right one slot
data[move_item] = data[move_item - 1]
move_item -= 1

data[move_item] = insert
Divide and Conquer Algorithms

• Break up problem into several parts and solve each part recursively divide

• Repeat until parts are so small that solution is immediate conquer

• Combine solutions to sub-problems into overall solution.


Merge sort
• Observation: linear time algorithm to merge to
sorted lists

list_a 1 3 4 7

list_b 2 5 6 8 9 10

output
Merge sort
• Observation: linear time algorithm to merge to
sorted lists

list_a 1 3 4 7

list_b 2 5 6 8 9 10

output
Merge sort
• Observation: linear time algorithm to merge to
sorted lists

list_a 1 3 4 7

list_b 2 5 6 8 9 10

output 1
Merge sort
• Observation: linear time algorithm to merge to
sorted lists

list_a 1 3 4 7

list_b 2 5 6 8 9 10

output 1
Merge sort
• Observation: linear time algorithm to merge to
sorted lists

list_a 1 3 4 7

list_b 2 5 6 8 9 10

output 12
Merge sort
• Observation: linear time algorithm to merge to
sorted lists

list_a 1 3 4 7

list_b 2 5 6 8 9 10

output 1 2 3
Merge sort
• Observation: linear time algorithm to merge to
sorted lists

list_a 1 3 4 7

list_b 2 5 6 8 9 10

output 1 2 3 4
Merge sort
• Observation: linear time algorithm to merge to
sorted lists

list_a 1 3 4 7

list_b 2 5 6 8 9 10

output 1 2 3 4 5
Merge sort
• Observation: linear time algorithm to merge to
sorted lists

list_a 1 3 4 7

list_b 2 5 6 8 9 10

output 1 2 3 4 5 6
Merge sort
• Observation: linear time algorithm to merge to
sorted lists

list_a 1 3 4 7

list_b 2 5 6 8 9 10

output 1 2 3 4 5 6 7
Merge sort
• Observation: linear time algorithm to merge to
sorted lists

list_a 1 3 4 7

list_b 2 5 6 8 9 10

output 1 2 3 4 5 6 7 8 9 10
Merge Sort Algorithm

merge_sort(list):

if len(list) > 1:

list_a = merge_sort( rst half of list) Divide

list_b = merge_sort(second half of list)

return merge of list_a and list_b

else
Conquer
return list
fi
Merge Sort Algorithm
log(n) Splitting: n

divide

conquer

merge

nlog(n)
log(n)
Summary

• Recursion
• Recursion vs. Iteration
• Search and Sorting
• Divide and Conquer

You might also like