Lecture 06 Recursion
These are my notes for SICP(Structure and Interpretation of Computer Programs). Hope they’ll be of some help to you.
General Description
Recursion is useful for solving problems with a naturally repeating structure - they are defined in terms of themselves.
It requires you to find patterns of smaller problems, and to define the smallest problem possible.
Recursive Functions
- A function is called recursive if the body of that function calls itself, either directly or indirectly
- This implies that executing the body of a recursive function may require applying that function multiple times
- Recursion is inherently tied to functional abstraction
Structure of a Recursive Function
- One or more base cases, usually the smallest input
- One or more ways of reducing the problem, and then solving the smaller problem using recursion
- One or more ways of using the solution to each smaller problem to solve our larger problem.
def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1)
How to Trust Functional Abstraction
Verifying the correctness of recursive functions
- Verify that the base cases work as expected
- For each larger case, verify that it works by assuming the smaller recursive calls are correct
Some Examples
Count Up
Let’s implement a recursive function to print the numbers from 1 to
n
. Assumen
is positive.
def count_up(n):
if n == 1:
print(1)
else:
count_up(n-1)
print(n)
Sum Digits
Let’s implement a recursive function to sum all the digits of
n
. Assumen
is positive.
def sum_digits(n):
if n < 10:
return n
return sum_digits(n // 10) + (n % 10)
Cascade
Print out a cascading tree of a positive integer n.
def cascade(n):
if n < 10:
print(n)
else:
print(n)
cascade(n // 10)
print(n)
#等价形式
def cascade(n):
print(n)
if n >= 10:
cascade(n // 10)
print(n)
Counting Partitions
Count the number of ways to give out
n
(>0) pieces of chocolate if nobody can have more thanm
(>0) pieces.
Ideas: (take count_part(6, 4) as an example)
- Find simpler instances of the problem
- Explore two possibilities:
- Use a 4
- Don’t use a 4
- Solve two simpler problems:
- count_part(2, 4)
- count_part(6, 3)
- Sum up the results of these smaller problems!
How do we know we’re done?
- If
n
is negative, then we cannot get to a valid partition - If
n
is 0, then we have arrived at a valid partition - If the largest piece we can use is 0, then we cannot get to a valid partition
def count_part(n, m):
if n == 0:
return 1
elif n < 0:
return 0
elif m == 0:
return 0
else:
with_m = count_part(n-m, m)
without_m = count_part(n, m-1)
return with_m + without_m
Summary
-
Recursive functions are functions that call themselves in their body one or more times
- This allows us to break the problem down into smaller pieces
- Using functional abstraction, we do not have to worry about how those smaller problems are solved
-
A recursive function has a base case to define its smallest problem, and one or more recursive calls
- If we know the base case is correct, and that we get the correct solution assuming the recursive calls work, then we know the function is correct
-
Evaluating recursive calls follow the same rules we’ve talked about so far
-
Recursion has three main components
- Base case/s: The simplest form of the problem
- Recursive call/s: Smaller version of the problem
- Use the solution to the smaller version of the problem to arrive at the solution to the original problem
-
Tree recursion makes multiple recursive calls and explores different choices
-
Use doctests and your own examples to help you figure out the simplest forms and how to make the problem smaller