这一章的开头讲了两种递归的方式,树状和线性。其实就是原版SICP第一章讲过的。
(1) 书上给出了一个很精巧的程序用来描述一个过程,这个过程将树状递归的函数通过空间换时间的方式改写成一个线性递归的结构。
这段代码的精巧之处在于,通过f参数来绑定原来的递归函数。然后通过memo函数的返回值将原来递归函数的名字绑定在memorized这个函数上面。这样一旦调用f,它内部的两个fib就不再调用本身,而是调用新的memoized函数。这就构造出了一个非常奇特的现象: 在fib里面调用的fib不再是原本的fib。
另外,为了通用性,可以将参数改成一个tuple类型。
def fib(n):
print('run f')
if n == 1:
return 0
if n == 2:
return 1
return fib(n-2) + fib(n-1)#fib = memo(fib)改变了函数名
def memo(f):
"""Return a memoized version of single-argument function f."""
cache = {}
def memoized(*args):
if tuple(args) not in cache:
cache[tuple(args)] = f(*args)
return cache[tuple(args)]
return memoized
fib = memo(fib)
例子: 给定任意的钱a元,以及指定的零钱种类kinds={c1,c2…cn},求出可以换成零钱的方式。
1: 直接进行树状递归:考虑对于任何一种状态,可以拿第一种零钱来换,也可以不拿。那么问题就被分解成两个子问题。
def count_changes(a,kinds = (1,5,10,25,50)):
if a==0:return 1
if a<0 or len(kinds)==0:
return 0
return count_changes(a,kinds[1:])+count_changes(a-kinds[0],kinds)
count_changes = memo(count_changes)
2 :
count_changes = memo(count_changes)
3 :迭代形式,相对比较难。需要仔细分析子问题和原问题的依赖关系。利用了动态规划的思想。在书里面留作一个问题.
def count_changes_iter(a,kinds=(1,5,10,25,50)):
def solve(record,i,j):
if i==0:return 1
if i<0 or j<=0:return 0
return record[i][j]
m,n = a+1,len(kinds)+1
record = [[0]*(n+1) for i in range(m+1)]
for k in range(0,m+n-1):
for i in range(0,k+1):
j = k-i
if i >=m or j<0 or j>=n:continue
if j!=0:d = kinds[n-j-1]
else:d = a+1
record[i][j] = solve(record,i,j-1)+solve(record,i-d,j)
return record[a][n-1]
print(count_changes_iter(1000))
def count_changes_iter(a,kinds=(1,5,10,25,50)):
def solve(record,i,j):
if i==0:return 1
if i<0 or j<=0:return 0
return record[i][j]
m,n = a+1,len(kinds)+1
record = [[0]*(n+1) for i in range(m+1)]
for k in range(0,m+n-1):
for i in range(0,k+1):
j = k-i
if i >=m or j<0 or j>=n:continue
if j!=0:d = kinds[n-j-1]
else:d = a+1
record[i][j] = solve(record,i,j-1)+solve(record,i-d,j)
return record[a][n-1]
print(count_changes_iter(1000))