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

Chapter_6_Recursion

Uploaded by

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

Chapter_6_Recursion

Uploaded by

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

Chapter 6.

Recursion

2024-2025
COMP1117A Computer Programming
Dr. T.W. Chim ([email protected]) & Dr. H.F. Ting ([email protected])
Department of Computer Science, The University of Hong Kong
We are going to learn…
Recursion and memorization
Understand the recursive strategy to tackle the following problems
Factorial
Palindrome
Greatest Common Divisor
Fibonacci number
MergeSort
Tower of Hanoi

2
Section 6.1

Recursion
Recursion
A function definition may contain a call to the function being defined.
In such cases, the function is said to be recursive.

Please implement a program


that reads in an integer n, and
outputs n! (factorial)
e.g., 5! = 5*4*3*2*1 = 120
4
Iterative vs. recursive
720
Option 1.
Use an iterative def factorialIterative(n):
approach to compute result = 1
the factorial of n, i.e. n! for i in range(2,n+1):
result *= i
return result

print(factorialIterative(6))

5
Iterative vs. recursive
For recursive approach, we need the following: Option 2 :
Base case – the smallest problem with solution available. Another option is
E.g., 0! = 1. to use a recursive
Divide and conquer – breaking a larger problem into approach to
smaller problem(s). compute n!

E.g., n! = n * (n - 1)!
Larger problem Smaller problem
6
Iterative vs. recursive
def f(n):
if n == 0: Base case
return 1
If n is 0, that means finding 0!,
else:
which is simply 1.
return n * f(n-1)

print(f(6))

7
Iterative vs. recursive
def f(n):
if n == 0: Progress
return 1
If n is not 0, we need to obtain the
else:
result of (n-1)!, with that result,
return n * f(n-1)
we simply need to multiply that by
n to obtain n!
print(f(6))

Well, I kind of understand the


strategy, but how this program works
with the strategy? Can we try to
simulate the execution for, say, f(6)?
8
Iterative vs. recursive
def f(n):
if n == 0: Calling f(6)
return 1 Calling f(6) is just like running
else:
normal functions, with local
return n * f(n-1)
variable n defined for the
execution of f(6).
print(f(6))
return 6 * f(5)
f(6)
Power of recursion : To find the
n 6 answer of f(6), we find f(5) first.
return 6 * f(6-1)
The execution of f(6) is
return 6 * f(5)
SUSPENDED at this stage.
9
Iterative vs. recursive
def f(n): There is another
if n == 0:
workspace when we
return 1
else: execute f(5), with n equals
return n * f(n-1) 5 in that workspace.
The recursive call is
print(f(6)) reducing n towards 0.
f(6) f(5)

n 6 n 5

return 6 * f(6-1) return 5 * f(5-1)

return 6 * f(5) return 5 * f(4)

10
Iterative vs. recursive
def f(n): Eventually, f(0) will be
if n == 0:
called. In that case, n equals
return 1
else: 0, and we will have no
return n * f(n-1) further recursive calls.
The value 1 is returned in
print(f(6)) f(0).
f(6) f(5) f (4) f(3) f(2) f(1) f(0)

n 6 n 5 n 4 n 3 n 2 n 1 n 0
return 6 * f(6-1) return 5 * f(5-1) return 4 * f(4-1) return 3 * f(3-1) return 2 * f(2-1) return 1 * f(1-1) return 1

return 6 * f(5) return 5 * f(4) return 4 * f(3) return 3 * f(2) return 2 * f(1) return 1 * f(0)

11
Iterative vs. recursive
def f(n): With f(0) return, the
if n == 0:
execution goes back to f(1)
return 1
else: and continue the suspended
return n * f(n-1) part with f(0) = 1 to the
return statement of f(1).
print(f(6))
f(6) f(5) f (4) f(3) f(2) f(1) f(0)

n 6 n 5 n 4 n 3 n 2 n 1 n 0
return 6 * f(6-1) return 5 * f(5-1) return 4 * f(4-1) return 3 * f(3-1) return 2 * f(2-1) return 1 * f(1-1) return 1

return 6 * f(5) return 5 * f(4) return 4 * f(3) return 3 * f(2) return 2 * f(1) return 1 * f(0)

return 1 * 1

return 1 12
Iterative vs. recursive
def f(n): So this is why we need to have
if n == 0:
return n * f(n-1)
return 1
else: But not just
return n * f(n-1) n * f(n-1)

print(f(6))
f(6) f(5) f (4) f(3) f(2) f(1) f(0)

n 6 n 5 n 4 n 3 n 2 n 1 n 0
return 6 * f(6-1) return 5 * f(5-1) return 4 * f(4-1) return 3 * f(3-1) return 2 * f(2-1) return 1 * f(1-1) return 1

return 6 * f(5) return 5 * f(4) return 4 * f(3) return 3 * f(2) return 2 * f(1) return 1 * f(0)

return 2 * 1 return 1 * 1

return 2 return 1 13
Iterative vs. recursive
def f(n): So this is why we need to have
if n == 0:
return n * f(n-1)
return 1
else: But not just
return n * f(n-1) n * f(n-1)

print(f(6))
f(6) f(5) f (4) f(3) f(2) f(1) f(0)

n 6 n 5 n 4 n 3 n 2 n 1 n 0
return 6 * f(6-1) return 5 * f(5-1) return 4 * f(4-1) return 3 * f(3-1) return 2 * f(2-1) return 1 * f(1-1) return 1

return 6 * f(5) return 5 * f(4) return 4 * f(3) return 3 * f(2) return 2 * f(1) return 1 * f(0)

return 3 * 2 return 2 * 1 return 1 * 1

return 6 return 2 return 1 14


Iterative vs. recursive
def f(n): So this is why we need to have
if n == 0:
return n * f(n-1)
return 1
else: But not just
return n * f(n-1) n * f(n-1)

print(f(6))
f(6) f(5) f (4) f(3) f(2) f(1) f(0)

n 6 n 5 n 4 n 3 n 2 n 1 n 0
return 6 * f(6-1) return 5 * f(5-1) return 4 * f(4-1) return 3 * f(3-1) return 2 * f(2-1) return 1 * f(1-1) return 1

return 6 * f(5) return 5 * f(4) return 4 * f(3) return 3 * f(2) return 2 * f(1) return 1 * f(0)

return 4 * 6 return 3 * 2 return 2 * 1 return 1 * 1

return 24 return 6 return 2 return 1 15


Iterative vs. recursive
def f(n): So this is why we need to have
if n == 0:
return n * f(n-1)
return 1
else: But not just
return n * f(n-1) n * f(n-1)

print(f(6))
f(6) f(5) f (4) f(3) f(2) f(1) f(0)

n 6 n 5 n 4 n 3 n 2 n 1 n 0
return 6 * f(6-1) return 5 * f(5-1) return 4 * f(4-1) return 3 * f(3-1) return 2 * f(2-1) return 1 * f(1-1) return 1

return 6 * f(5) return 5 * f(4) return 4 * f(3) return 3 * f(2) return 2 * f(1) return 1 * f(0)

return 5 * 24 return 4 * 6 return 3 * 2 return 2 * 1 return 1 * 1

return 120 return 24 return 6 return 2 return 1 16


Iterative vs. recursive 720
720
def f(n): def factorialIterative(n):
if n == 0: result = 1
return 1 for i in range(2,n+1):
else:
return n * f(n-1) V.S. result *= i
return result

print(f(6)) print(factorialIterative(6))
f(6) f(5) f (4) f(3) f(2) f(1) f(0)

n 6 n 5 n 4 n 3 n 2 n 1 n 0
return 6 * f(6-1) return 5 * f(5-1) return 4 * f(4-1) return 3 * f(3-1) return 2 * f(2-1) return 1 * f(1-1) return 1

return 6 * f(5) return 5 * f(4) return 4 * f(3) return 3 * f(2) return 2 * f(1) return 1 * f(0)

return 6 * 120 return 5 * 24 return 4 * 6 return 3 * 2 return 2 * 1 return 1 * 1

return 720 return 120 return 24 return 6 return 2 return 1 17


Iterative vs. recursive
If a recursive call is encountered:
Temporarily suspends its
computation in the current function.

Proceeds to evaluate the recursive call.

Returns to finish the outer computation


if the recursive call is completed.

18
Example 1 Recursive palindrome
-ve index -ve index
-6 -5 -4 -3 -2 -1 -6 -5 -4 -3 -2 -1
+ve index 0 1 2 3 4 5 0 1 2 3 4 5 +ve index
b a c c a b b a c k a b

Iterative:
def isPalindrome(s):
If the first slot is NOT equal for i in range(0, len(s)//2):
to the last slot, it is not a if s[i] != s[-(i+1)]:
palindrome. return False
Then, if the 2nd slot is NOT return True
Iterative approach
equal to the 2nd last slot, it is
not palindrome, …etc Note: String negative index counts from the
last character (-1, -2, …) 19
Example 1 Recursive palindrome
0 1 2 3 4 5
b a c c a b
len(s)=6 Larger problem
0 1 2 3
a c c a
len(s)=4 Smaller problem

Question 1. The length of the string is the


What is the parameter that determines
parameter that the problem size. (i.e., the
determines the problem is smaller when
problem size? len(s) is smaller)
20
Example 1 Recursive palindrome
0 1 2 3 4 5 0 1 2 3 4 5 6
b a c c a b b a c d c a b

0 1 2 3 0 1 2 3 4
a c c a a c d c a

0 1 0 1 2
c c c d c

0
Empty string Base cases d

Question 2. What
is/are the base case(s) When s is empty
that the solution is string or len(s)==1,
readily available? s is a palindrome.
21
Example 1 Recursive palindrome
0 1 2 3 4 5 6
b a c d c a b

Question 3. What is the progress to


break down the problem and reuse
the solution of the smaller problem?

22
Example 1 Recursive palindrome
Ok, the first and the last slots are the same. 0 1 2 3 4 5 6
I want to ask if acdca is a palindrome? b a c d c a b

Ok, the first and the last slots are the same. 0 1 2 3 4
I want to ask if cdc is a palindrome? Progress a c d c a
towards
the base
Ok, the first and the last slots are the same.
I want to ask if d is a palindrome?
case 0 1 2
c d c

Yes! d is a 0
palindrome! d

base case
23
Example 1 Recursive palindrome
Yes! bacdcab is 0 1 2 3 4 5 6
a palindrome! b a c d c a b

Yes! acdca is a 0 1 2 3 4
palindrome!
a c d c a
Reuse of
answer of
smaller Yes! cdc is a 0 1 2
palindrome!
problem c d c

Yes! d is a 0
palindrome! d

base case
24
Example 1 Recursive palindrome
Ok, the first and the last slots are the same. No! bacdkab is 0 1 2 3 4 5 6
I want to ask if acdka is a palindrome? NOT a
palindrome! b a c d k a b

Ok, the first and the last slots are the same. No! acdka is NOT 0 1 2 3 4
I want to ask if cdk is a palindrome? a palindrome!
a c d k a

The first and the last slots are NOT the No! cdk is NOT a 0 1 2
same. palindrome!
c d k

25
Example 1 Recursive palindrome
def isPalindrome(s):
if len(s)<=1: # Base case
return True
else: # Progress
if s[0] == s[-1]:
return isPalindrome(s[1:-2])
else:
return False
Recursive approach Base case
If length of the string is less than
or equal to 1, that must be a
palindrome.
26
Example 1 Recursive palindrome
def isPalindrome(s):
if len(s)<=1: # Base case
return True
else: # Progress
if s[0] == s[-1]:
return isPalindrome(s[1:-2])
else:
return False
Recursive approach Not a palindrome
If the string’s first character is NOT
equal to the last one, immediately
conclude that it is NOT a
palindrome. 27
Example 1 Recursive palindrome
def isPalindrome(s):
if len(s)<=1: # Base case
return True
else: # Progress
if s[0] == s[-1]: s[1:-1] = a substring of s, starting
return isPalindrome(s[1:-1]) from index 1 and stopping at
else: index (-1 – 1 = -2)

return False
Recursive approach Progress towards base case
If the first and last slots of the
string are the same, then whether
the current string is a palindrome
depends on whether the internal
string is a palindrome. 28
Example 1 Recursive palindrome
def isPalindrome(s):
if len(s)<=1: # Base case
Now, let’s try to
return True
else: # Progress simulate what will
if s[0] == s[-1]: happen when we
return isPalindrome(s[1:-1])
run this program.
else:
return False

isPalindrome('backab') isPalindrome('acka') isPalindrome('ck')

s='backab' s='acka' s='ck'


return isPalindrome(s[1:-1]) return isPalindrome(s[1:-1])
return isPalindrome('acka') return isPalindrome('ck')

29
Example 1 Recursive palindrome
def isPalindrome(s):
if len(s)<=1: # Base case More importantly,
return True understand how return
else: # Progress isPalindrome(s[1:-2])
if s[0] == s[-1]: works in propagating
return isPalindrome(s[1:-1]) the result back the
else: recursive calls.
return False

isPalindrome('backab') isPalindrome('acka') isPalindrome('ck')

s='backab' s='acka' s='ck'


return isPalindrome(s[1:-1]) return isPalindrome(s[1:-1]) return False
return isPalindrome('acka') return isPalindrome('ck')
return False return False 30
Example 1 Recursive palindrome
def isPalindrome(s):
if len(s)<=1: # Base case
return True
else: # Progress
if s[0] == s[-1]:
return isPalindrome(s[1:-1])
else:
return False

isPalindrome('baccab') isPalindrome('acca') isPalindrome('cc') isPalindrome('')

s='baccab' s='acca' s='cc' s=''


return isPalindrome(s[1:-1]) return isPalindrome(s[1:-1]) return isPalindrome(s[1:-1]) return True
return isPalindrome('acca') return isPalindrome('cc') return isPalindrome('')

31
Example 1 Recursive palindrome
def isPalindrome(s):
if len(s)<=1: # Base case
return True
else: # Progress
if s[0] == s[-1]:
return isPalindrome(s[1:-1])
else:
return False

isPalindrome('baccab') isPalindrome('acca') isPalindrome('cc') isPalindrome('')

s='baccab' s='acca' s='cc' s=''


return isPalindrome(s[1:-1]) return isPalindrome(s[1:-1]) return isPalindrome(s[1:-1]) return True
return isPalindrome('acca') return isPalindrome('cc') return isPalindrome('')

return True return True return True 32


Example 2. Greatest Common Divisor
Find the greatest common divisor (GCD) of two numbers, which is the
largest number that divides both of them without leaving a remainder.
GCD (504,105) = GCD (504%105, 105)
= GCD (84,105)

If x can divide 105 and 504 Do you still remember how to solve
x can also divide 84 because: GCD(a,b)?
Rather than keeping on replacing the
84+(105*4)=504 larger number by the difference
between the two numbers until the
Therefore x can also x can divide x can divide
3 2 1 two numbers are the same, we can
divide this part this part this part
use remainder calculation.
33
Example 2. Greatest Common Divisor
Find the greatest common divisor (GCD) of two numbers, which is the
largest number that divides both of them without leaving a remainder.
GCD (504,105) = GCD (504%105, 105)
= GCD (84,105)
Let’s always rearrange the smaller
= GCD (105,84) value to be the 2nd parameter

= GCD (84,105%84)
= GCD (84,21)
= GCD (21,84%21)
= GCD (21,0) GCD(x,0) = x because
only x can divide x and 0.
= 21 34
Example 2. Greatest Common Divisor

GCD (504,105) = GCD (504%105, 105) def gcd(a, b):


= GCD (84,105) if (b == 0): #Base case
= GCD (105,84) return a
else: #Progress
= GCD (84,105%84) return gcd(b , a % b)
= GCD (84,21)
= GCD (21,84%21)
= GCD (21,0)
= 21 35
Example 2. Greatest Common Divisor

GCD (504,105) = GCD (504%105, 105) def gcd(a, b):


= GCD (84,105) if (b == 0): #Base case
= GCD (105,84) return a
else: #Progress
= GCD (84,105%84) return gcd(b , a % b)
= GCD (84,21)
= GCD (21,84%21) Note that a % b must be smaller than b.
= GCD (21,0)
= 21 36
Example 3. Fibonacci number
The Fibonacci numbers are the numbers in an integer sequence, called
the Fibonacci sequence, and characterized by the fact that every number
after the first two is the sum of the two preceding ones.
0 1 2 3 4 5 6 7 8 9 10 11 12
Fibonacci sequence …
0 1 1 2 3 5 8 13 21 34 55 89 144

Please write a Question 1. What


Python program to is the parameter
return the n-th that determines
Fibonacci number. the problem size?
37
Example 3. Fibonacci number
The Fibonacci numbers are the numbers in an integer sequence, called
the Fibonacci sequence, and characterized by the fact that every number
after the first two is the sum of the two preceding ones.
0 1 2 3 4 5 6 7 8 9 10 11 12
Fibonacci sequence …
0 1 1 2 3 5 8 13 21 34 55 89 144

Question 2. What
Base cases: is/are the base
If n<2, the case(s) that the
answer is n solution is readily
available? 38
Example 3. Fibonacci number
The Fibonacci numbers are the numbers in an integer sequence, called
the Fibonacci sequence, and characterized by the fact that every number
after the first two is the sum of the two preceding ones.
0 1 2 3 4 5 6 7 8 9 10 11 12
Fibonacci sequence …
0 1 1 2 3 5 8 13 21 34 55 89 144

Question 3. What is
Progress:
the progress to
To find the nth Fibonacci number,
break down the
we need to have the n-1th and
problem and reuse
the n-2th Fibonacci number first,
the solution of the
and sum them up.
smaller problem?
39
Example 3. Fibonacci number
def f(n):
if n<2: #Base case
return n
else: #Progress
return f(n-1)+f(n-2)

40
Example 3. Fibonacci number
def f(n):
if n<2: #Base case
return n
else: #Progress
return f(n-1)+f(n-2)

f(5)
Now, let’s try to
f(4) f(3)
simulate what will
f(3) f(2) f(2) f(1)
happen when we
f(2) f(1) f(1) f(0) f(1) f(0) run f(5).
f(1) f(0)

41
Recursive calls
Example 3. Fibonacci number
def f(n):
if n<2: #Base case
return n
else: #Progress
return f(n-1)+f(n-2)

f(5)
This is how the
f(4) f(3)
return from base
f(3) f(2) f(2) f(1)
case propagate back
1 f(1) f(1) f(0) f(1) f(0) to the parent calls.
1 0

Return values from base case 42


Example 3. Fibonacci number
def f(n):
if n<2: #Base case
return n
else: #Progress
return f(n-1)+f(n-2)

f(5)

f(4) f(3)

2 f(2) f(2) f(1)

1 1 f(1) f(0) f(1) f(0)

43
Example 3. Fibonacci number
def f(n):
if n<2: #Base case
return n
else: #Progress
return f(n-1)+f(n-2)

f(5)

f(4) f(3)

2 1 f(2) f(1)

1 0 f(1) f(0)

44
Example 3. Fibonacci number
def f(n):
if n<2: #Base case
return n
else: #Progress
return f(n-1)+f(n-2)

f(5)

3 f(3)

2 1 f(2) f(1)

f(1) f(0)

45
Example 3. Fibonacci number
def f(n):
if n<2: #Base case
return n
else: #Progress
return f(n-1)+f(n-2)

f(5)

3 f(3)

1 f(1)

1 0

46
Example 3. Fibonacci number
def f(n):
if n<2: #Base case
return n
else: #Progress
return f(n-1)+f(n-2)

f(5)

3 2

1 1

47
Example 3. Fibonacci number
def f(n):
if n<2: #Base case
return n
else: #Progress
return f(n-1)+f(n-2)

5 And finally, we
3 2 found that f(5) =
5, do you observe
Observation: There are any efficiency
many repeat function problem in the
calls! I try to call f(100), computation?
it is extremely slow! 48
Section 6.2

Memorization
Example 3. Fibonacci number
Implement a map that caches the computed Fibonacci numbers and avoid
repeat function calls.
cache = {} # Create a global map

def f(n):
if n in cache:
return cache[n] A global map object
if n<2: cache is a variable referencing a map
value = n object, which is initially empty.
else: The map will have key as the value of n,
value = f(n-1)+f(n-2) value storing the nth Fibonacci number.
cache[n] = value
return value 50
Example 3. Fibonacci number
Implement a map that caches the computed Fibonacci numbers and avoid
repeat function calls.
cache = {} # Create a global map

def f(n):
if n in cache:
return cache[n] Check if f(n) has been computed
if n<2: If n is in cache, then it has been
value = n computed before and we can directly
else: return its cached value without need of
value = f(n-1)+f(n-2)
recursive calls.
cache[n] = value
return value 51
Example 3. Fibonacci number
Implement a map that caches the computed Fibonacci numbers and avoid
repeat function calls.
cache = {} # Create a global map

def f(n):
if n in cache:
return cache[n] If not cached, compute recursively
if n<2: If n is NOT in cache, then we need to
value = n recursively compute its value.
else:
value = f(n-1)+f(n-2)

cache[n] = value
return value 52
Example 3. Fibonacci number
Implement a map that caches the computed Fibonacci numbers and avoid
repeat function calls.
cache = {} # Create a global map

def f(n):
if n in cache:
return cache[n] Update the cache
if n<2: Up to this point the value of the nth
value = n Fibonacci number has been computed,
else: we update the cache to store the newly
value = f(n-1)+f(n-2)
computed number.
cache[n] = value
return value 53
Example 3. Fibonacci number
Now, let’s try to simulate what
will happen when we run f(5).

cache = {} # Create a global map Key


Cache Empty
value
def f(n):
if n in cache: f(5)
return cache[n]
f(4) f(3)
if n<2:
value = n f(3) f(2)
else:
value = f(n-1)+f(n-2) f(2) f(1)

cache[n] = value f(1) f(0)


return value 54
Example 3. Fibonacci number
f(0) = 0 and f(1) = 1 are computed
and stored in the cache.

cache = {} # Create a global map Key 0 1


Cache
value 0 1
def f(n):
if n in cache: f(5)
return cache[n]
f(4) f(3)
if n<2:
value = n f(3) f(2)
else:
value = f(n-1)+f(n-2) f(2) f(1)

cache[n] = value 1 0
return value 55
Example 3. Fibonacci number
f(2) = 1 is computed and
stored in the cache.
cache = {} # Create a global map Key 0 1 2
Cache
value 0 1 1
def f(n):
if n in cache: f(5)
return cache[n]
f(4) f(3)
if n<2:
value = n f(3) f(2)
else:
value = f(n-1)+f(n-2) f(2) f(1)

cache[n] = value 1 0
return value 56
Example 3. Fibonacci number
Cache hit! f(1) has computed before, so
its value, i.e.,1, is returned right away.

cache = {} # Create a global map Key 0 1 2


Cache
value 0 1 1
def f(n):
if n in cache: f(5)
return cache[n]
f(4) f(3)
if n<2:
value = n f(3) f(2)
else:
value = f(n-1)+f(n-2) 1 f(1)

cache[n] = value
return value 57
Example 3. Fibonacci number
f(3) = 2 is computed and
stored in the cache.
cache = {} # Create a global map Key 0 1 2 3
Cache
value 0 1 1 2
def f(n):
if n in cache: f(5)
return cache[n]
f(4) f(3)
if n<2:
value = n f(3) f(2)
else:
value = f(n-1)+f(n-2) 1 1

cache[n] = value
return value 58
Example 3. Fibonacci number
Cache hit! f(2) has computed before, so
its value, i.e.,1, is returned right away.

cache = {} # Create a global map Key 0 1 2 3


Cache
value 0 1 1 2
def f(n):
if n in cache: f(5)
return cache[n]
f(4) f(3)
if n<2:
value = n 2 f(2)
else:
value = f(n-1)+f(n-2)

cache[n] = value
return value 59
Example 3. Fibonacci number
f(4) = 3 is computed and
stored in the cache.
cache = {} # Create a global map Key 0 1 2 3 4
Cache
value 0 1 1 2 3
def f(n):
if n in cache: f(5)
return cache[n]
f(4) f(3)
if n<2:
value = n 2 1
else:
value = f(n-1)+f(n-2)

cache[n] = value
return value 60
Example 3. Fibonacci number
Cache hit! f(3) has computed before, so its
value, i.e.,2, is returned right away.

cache = {} # Create a global map Key 0 1 2 3 4


Cache
value 0 1 1 2 3
def f(n):
if n in cache: f(5)
return cache[n]
3 f(3)
if n<2:
value = n
else:
value = f(n-1)+f(n-2)

cache[n] = value
return value 61
Example 3. Fibonacci number

cache = {} # Create a global map Key 0 1 2 3 4 5


Cache
value 0 1 1 2 3 5
def f(n):
if n in cache: f(5)
return cache[n]
3 2
if n<2:
value = n
else:
Finally, f(5) is computed.
value = f(n-1)+f(n-2) Please try to run f(100) and
compare its performance with
cache[n] = value
return value the no cache version.
62
Example 4. MergeSort
54 26 93 17 77 31 44 55
Sorting a list recursively:
Question 1. What is the
parameter that determines
the problem size?

Obviously it is the
number of items in
the list, say n.
E.g., n=8 in this
17 26 31 44 54 55 77 93 example.
63
Example 4. MergeSort
54 26 93 17 77 31 44 55

54 26 93 17 77 31 44 55 Divide
Divide the list with n items to two
54 26 93 17 77 31 44 55 lists of n/2 items and sort them

54 26 93 17 77 31 44 55

Base cases: Question 2. What


If n==1, the list is is/are the base case(s)
already sorted. that the solution is
readily available?
64
Example 4. MergeSort
54 26 93 17 77 31 44 55
Question 3. How to
reuse the solution of
54 26 93 17 77 31 44 55 the smaller problem?
54 26 93 17 77 31 44 55

54 26 93 17 77 31 44 55

26 54 17 93
Given two sorted lists of size n/2,
Compare 26 & 17, insert 17 to a new list. merging the two lists to create a
Compare 26 & 93, insert 26 to the new list.
Compare 54 & 93, insert 54 to the new list.
Finally, insert 93 to the new list.
sorted list of size n requires
The new list contains 17, 26, 54, 93.
Done!
scanning the two lists once only.
65
Example 4. MergeSort
54 26 93 17 77 31 44 55

54 26 93 17 77 31 44 55 Divide
Divide the list with n items to two
54 26 93 17 77 31 44 55 lists of n/2 items and sort them

54 26 93 17 77 31 44 55

26 54 17 93 31 77 44 55
Merge
17 26 54 93 31 44 55 77 Merge two sorted lists of size n/2 to
create a sorted list of n items

17 26 31 44 54 55 77 93

66
Example 4. MergeSort def mergeSort(A,left,right):
mergeSort(A,0,8)
if right-left>1:
54 26 93 17 77 31 44 55 mid = (left+right)//2
mergeSort(A, left, mid)
mergeSort(A, mid, right)
mergeSort(A,0,4) mergeSort(A,4,8) merge(A, left, mid, right)
54 26 93 17 77 31 44 55
A = [54,26,93,17,77,31,44,55]
mergeSort(A,0,len(A))
mergeSort(A,0,2) mergeSort(A,2,4) mergeSort(A,4,6) mergeSort(A,6,8)

54 26 93 17 77 31 44 55 right-left > 1 is a
condition to detect
mergeSort mergeSort mergeSort mergeSort mergeSort mergeSort mergeSort mergeSort
(A,0,1) (A,1,2) (A,2,3) (A,3,4) (A,4,5) (A,5,6) (A,6,7) (A,7,8) progress cases.
54 26 93 17 77 31 44 55

If there are 2 elements, right – left = 2.


If there is only 1 element, right – left = 1.
67
def merge(A,left,mid,right):
L = A[left:mid]
R = A[mid:right]
i=j=0
k=left def mergeSort(A,left,right):
while i < len(L) and j < len(R): if right-left>1:
if L[i] < R[j]: mid = (left+right)//2
A[k]=L[i] mergeSort(A, left, mid)
i=i+1 mergeSort(A, mid, right)
k=k+1 merge(A, left, mid, right)
else:
A[k]=R[j] A = [54,26,93,17,77,31,44,55]
j=j+1 mergeSort(A,0,len(A))
k=k+1

while i < len(L):


A[k]=L[i]
We call the merge()
i=i+1 function to combine
k=k+1
two sorted lists of size
while j < len(R): n/2 to a sorted list of
A[k]=R[j]
j=j+1 size n.
k=k+1
68
def merge(A,left,mid,right):
L = A[left:mid] merge(A,0,4,8)
R = A[mid:right]
i=j=0
k=left k
while i < len(L) and j < len(R):
if L[i] < R[j]: A 17 26 54 93 31 44 55 77 k=0
A[k]=L[i]
i=i+1
k=k+1 Sorted Sorted

else:
A[k]=R[j] i
j=j+1
k=k+1 L 17 26 54 93 i=0

while i < len(L):


A[k]=L[i] j

i=i+1
k=k+1
R 31 44 55 77 j=0
while j < len(R):
A[k]=R[j]
j=j+1
k=k+1
69
def merge(A,left,mid,right):
L = A[left:mid] merge(A,0,4,8)
R = A[mid:right]
i=j=0
k=left k
while i < len(L) and j < len(R):
if L[i] < R[j]: A 17 26 54 93 31 44 55 77 k=0
A[k]=L[i]
i=i+1
k=k+1 Sorted Sorted

else:
A[k]=R[j] i
j=j+1
k=k+1 L 17 26 54 93 i=0

while i < len(L):


A[k]=L[i] j

i=i+1
k=k+1
R 31 44 55 77 j=0
while j < len(R):
A[k]=R[j]
j=j+1 This part just put the smallest value
k=k+1 among L and R to A, one at a time.
70
def merge(A,left,mid,right):
L = A[left:mid] merge(A,0,4,8)
R = A[mid:right]
i=j=0
k=left k
while i < len(L) and j < len(R):
if L[i] < R[j]: A 17 26 54 93 31 44 55 77 k=1
A[k]=L[i]
i=i+1
k=k+1 Sorted Sorted

else:
A[k]=R[j] i
j=j+1
k=k+1 L 17 26 54 93 i=1

while i < len(L):


A[k]=L[i] j

i=i+1
k=k+1
R 31 44 55 77 j=0
while j < len(R):
A[k]=R[j]
j=j+1 This part just put the smallest value
k=k+1 among L and R to A, one at a time.
71
def merge(A,left,mid,right):
L = A[left:mid] merge(A,0,4,8)
R = A[mid:right]
i=j=0
k=left k
while i < len(L) and j < len(R):
if L[i] < R[j]: A 17 26 31 93 31 44 55 77 k=2
A[k]=L[i]
i=i+1
k=k+1 Sorted Sorted

else:
A[k]=R[j] i
j=j+1
k=k+1 L 17 26 54 93 i=2

while i < len(L):


A[k]=L[i] j

i=i+1
k=k+1
R 31 44 55 77 j=0
while j < len(R):
A[k]=R[j]
j=j+1 This part just put the smallest value
k=k+1 among L and R to A, one at a time.
72
def merge(A,left,mid,right):
L = A[left:mid] merge(A,0,4,8)
R = A[mid:right]
i=j=0
k=left k
while i < len(L) and j < len(R):
if L[i] < R[j]: A 17 26 31 44 31 44 55 77 k=3
A[k]=L[i]
i=i+1
k=k+1 Sorted Sorted

else:
A[k]=R[j] i
j=j+1
k=k+1 L 17 26 54 93 i=2

while i < len(L):


A[k]=L[i] j

i=i+1
k=k+1
R 31 44 55 77 j=1
while j < len(R):
A[k]=R[j]
j=j+1 This part just put the smallest value
k=k+1 among L and R to A, one at a time.
73
def merge(A,left,mid,right):
L = A[left:mid] merge(A,0,4,8)
R = A[mid:right]
i=j=0
k=left k
while i < len(L) and j < len(R):
if L[i] < R[j]: A 17 26 31 44 54 44 55 77 k=4
A[k]=L[i]
i=i+1
k=k+1 Sorted Sorted

else:
A[k]=R[j] i
j=j+1
k=k+1 L 17 26 54 93 i=2

while i < len(L):


A[k]=L[i] j

i=i+1
k=k+1
R 31 44 55 77 j=2
while j < len(R):
A[k]=R[j]
j=j+1 This part just put the smallest value
k=k+1 among L and R to A, one at a time.
74
def merge(A,left,mid,right):
L = A[left:mid] merge(A,0,4,8)
R = A[mid:right]
i=j=0
k=left k
while i < len(L) and j < len(R):
if L[i] < R[j]: A 17 26 31 44 54 55 55 77 k=5
A[k]=L[i]
i=i+1
k=k+1 Sorted Sorted

else:
A[k]=R[j] i
j=j+1
k=k+1 L 17 26 54 93 i=3

while i < len(L):


A[k]=L[i] j

i=i+1
k=k+1
R 31 44 55 77 j=2
while j < len(R):
A[k]=R[j]
j=j+1 This part just put the smallest value
k=k+1 among L and R to A, one at a time.
75
def merge(A,left,mid,right):
L = A[left:mid] merge(A,0,4,8)
R = A[mid:right]
i=j=0
k=left k
while i < len(L) and j < len(R):
if L[i] < R[j]: A 17 26 31 44 54 55 77 77 k=6
A[k]=L[i]
i=i+1
k=k+1 Sorted Sorted

else:
A[k]=R[j] i
j=j+1
k=k+1 L 17 26 54 93 i=3

while i < len(L):


A[k]=L[i] j

i=i+1
k=k+1
R 31 44 55 77 j=3
while j < len(R):
A[k]=R[j]
j=j+1 This part just put the smallest value
k=k+1 among L and R to A, one at a time.
76
def merge(A,left,mid,right):
L = A[left:mid] merge(A,0,4,8)
R = A[mid:right]
i=j=0
k=left k
while i < len(L) and j < len(R):
if L[i] < R[j]: A 17 26 31 44 54 55 77 93 k=7
A[k]=L[i]
i=i+1
k=k+1 Sorted Sorted

else:
A[k]=R[j] i
j=j+1
k=k+1 L 17 26 54 93 i=3

while i < len(L):


A[k]=L[i] j

i=i+1
k=k+1
R 31 44 55 77 j=4
while j < len(R):
A[k]=R[j]
Since all values in R are scanned,
j=j+1
we put the remaining values in L to A.
k=k+1
77
Example 5. Tower of Hanoi

n=4

A B C
The Tower of Hanoi is a mathematical game.
It consists of three rods (A,B,C) and n disks of different sizes which can slide
onto any rod.
Constraint: At any stage, no larger disk can be placed on top of smaller disk.
The objective of the puzzle is to move the entire stack from rod A to rod C.
Only one disk is allowed to move at a time. 78
Example 5. Tower of Hanoi

n=1

A B C

Base case: Can you solve


the above problem
instance n=1?

79
Example 5. Tower of Hanoi

n=2

A B C

Can you solve the


above problem
instance n=2?

80
Example 5. Tower of Hanoi

n=2

A B C

Strategy to move a tower of 2 disks:


Move the top 1 disk to B. (Problem of n=1)

81
Example 5. Tower of Hanoi

n=2

A B C

Strategy to move a tower of 2 disks:


Move the top 1 disk to B. (Problem of n=1)
Move the largest disk to C. (Problem of n=1)

82
Example 5. Tower of Hanoi

n=2

A B C

Strategy to move a tower of 2 disks:


Move the top 1 disk to B. (Problem of n=1)
Move the largest disk to C. (Problem of n=1)
Move the top 1 disk back to C. (Problem of n=1)

83
Example 5. Tower of Hanoi

n=3

A B C

(Important problem solving technique)


We know how to move a tower of 2 disks
from A to C using B as buffer.
Can we reuse the solution for n=2 to help to
solve this problem where n=3?
84
Example 5. Tower of Hanoi
Problem
for n = 2
n=2

A B C

Strategy to move a tower of 3 disks:


Step1. Move the top 2 disks from A to B, using C as buffer. (Problem of n=2)

85
Example 5. Tower of Hanoi
Problem
for n = 2
n=2

A B C

Strategy to move a tower of 3 disks:


Step1. Move the top 2 disks from A to B, using C as buffer. (Problem of n=2)
Step2. Move the largest disk from A to C. (Problem of n=1)

86
Example 5. Tower of Hanoi
Problem
for n = 2
n=2

A B C

Strategy to move a tower of 3 disks:


Step1. Move the top 2 disks from A to B, using C as buffer. (Problem of n=2)
Step2. Move the largest disk from A to C. (Problem of n=1)
Step3. Move the top 2 disks from B to C, using A as buffer. (Problem of n=2)

87
Example 5. Tower of Hanoi
Problem
n=3 for n = 3

A B C

Strategy to move a tower of 4 disks:


Step1. Move the top 3 disks from A to B, using C as buffer. (Problem of n=3)

88
Example 5. Tower of Hanoi
Problem
n=3 for n = 3

A B C

Strategy to move a tower of 4 disks:


Step1. Move the top 3 disks from A to B, using C as buffer. (Problem of n=3)
Step2. Move the largest disk from A to C. (Problem of n=1)

89
Example 5. Tower of Hanoi
Problem
n=3 for n = 3

A B C

Strategy to move a tower of 4 disks:


Step1. Move the top 3 disks from A to B, using C as buffer. (Problem of n=3)
Step2. Move the largest disk from A to C. (Problem of n=1)
Step3. Move the top 3 disks from B to C, using A as buffer. (Problem of n=3)

90
Example 5. Tower of Hanoi
def move(n, start, end, buffer):
if n == 1: # Base case
print("Moving disk from",start,"to",end)
else: # Progress
move(n-1,from, buffer, to)
move(1,from, to, buffer)
moveTower(n-1, buffer, to, from)

Base case:
Just move the disk from
the fromPole to the toPole
when height==1.
91
Example 5. Tower of Hanoi
def move(n, start, end, buffer):
if n == 1: # Base case
print("Moving disk from",start,"to",end)
else: # Progress
move(n-1, start, buffer, end)
move(1, start, end, buffer)
move(n-1, buffer, end, start)

Progress:
Make a n-1 problem to move the tower from start to buffer.
Make a 1 problem to move the tower from start to end.
Make a n-1 problem to move the tower from buffer to end.

92
Example 5. Tower of Hanoi
def move(n, start, end, buffer):
if n == 1: # Base case
print("Moving disk from",start,"to",end)
else: # Progress
move(n-1, start, buffer, end)
move(1, start, end, buffer)
move(n-1, buffer, end, start)

Now, let’s try to simulate what will happen


when we run move(3,"A","C","B").
move(3,"A","C","B")
To move the tower of 3 disks from A to C,
using B as buffer.
93
Example 5. Tower of Hanoi
def move(n, start, end, buffer):
if n == 1: # Base case
Now we recursively call
print("Moving disk from",start,"to",end) move(2,"A","B","C") to
else: # Progress
move(n-1, start, buffer, end) move the tower with 2
move(1, start, end, buffer) disks from A to B first.
move(n-1, buffer, end, start)

move(2,"A","B","C")

move(3,"A","C","B")

94
Example 5. Tower of Hanoi
def move(n, start, end, buffer):
if n == 1: # Base case move(1,"A","C","B")
print("Moving disk from",start,"to",end)
else: # Progress
move(n-1, start, buffer, end) move(1,"A","B","C")
move(1, start, end, buffer)
move(n-1, buffer, end, start)
move(1,"C","B","A")

move(2,"A","B","C")

move(3,"A","C","B")

95
Example 5. Tower of Hanoi
def move(n, start, end, buffer):
if n == 1: # Base case move(1,"A","C","B")
print("Moving disk from",start,"to",end)
else: # Progress
move(n-1, start, buffer, end) move(1,"A","B","C")
move(1, start, end, buffer)
move(n-1, buffer, end, start)
move(1,"C","B","A")

move(2,"A","B","C")

move(1,"A","C","B")
move(3,"A","C","B")

96
Example 5. Tower of Hanoi
def move(n, start, end, buffer):
if n == 1: # Base case move(1,"A","C","B")
print("Moving disk from",start,"to",end)
else: # Progress
move(n-1, start, buffer, end) move(1,"A","B","C")
move(1, start, end, buffer)
move(n-1, buffer, end, start)
move(1,"C","B","A")

move(2,"A","B","C")

move(1,"A","C","B")
move(3,"A","C","B")

move(2,"B","C","A")
97
Example 5. Tower of Hanoi
def move(n, start, end, buffer):
if n == 1: # Base case move(1,"A","C","B")
print("Moving disk from",start,"to",end)
else: # Progress
move(n-1, start, buffer, end) move(1,"A","B","C")
move(1, start, end, buffer)
move(n-1, buffer, end, start)
move(1,"C","B","A")

move(2,"A","B","C")

move(1,"B","A","C")
move(1,"A","C","B")
move(3,"A","C","B")
move(1,"B","C","A")

move(2,"B","C","A")
move(1,"A","C","B")
98
Example 5. Tower of Hanoi
1
Now, let’s try to use these 7 steps to see if move(1,"A","C","B")
you can solve the Tower of Hanoi problem 2
with n=3? move(1,"A","B","C")
Let’s try move(4,"A","C","B") as
3
well!
move(1,"C","B","A")

move(2,"A","B","C")
5
4 move(1,"B","A","C")
move(1,"A","C","B")
move(3,"A","C","B") 6
move(1,"B","C","A")

7
move(2,"B","C","A")
move(1,"A","C","B")
99
Example 5. Tower of Hanoi

A B C

100
Summary
To understand recursive strategy, you need to know…
The parameter that determines the problem size.
The base case(s) where the solution is readily available.
The progress to break down a bigger problem to smaller problem(s).
The strategy to reuse the solutions of the smaller problem(s) to solve the
bigger problem.

Learning objective: To understand the Python code and recursive


strategies presented in this chapter first.

101
102
Chapter 6.

END
Acknowledgement:
This set of slides is partially adopted from
ENGG1330. Thanks to Dr. C.K. Chui!

2024-2025
COMP1117A Computer Programming
Dr. T.W. Chim ([email protected]) & Dr. H.F. Ting ([email protected])
Department of Computer Science, The University of Hong Kong

You might also like