我几天前开始阅读 Systematic Program Design: From Clarity to Efficiency 这本书。第4章讨论了将任何递归算法转换为其对应迭代的系统方法。这似乎是一种非常强大的通用方法,但我很难理解它是如何工作的。
在阅读了一些关于使用自定义堆栈去除递归的文章之后,感觉这个提出的方法会产生更加可读,优化和紧凑的输出。
#NS: lcs and knap are using implicit variables (i.e.: defined globally), so they won't
#work directly
# n>=0
def fac(n):
if n==0:
return 1
else:
return n*fac(n-1)
# n>=0
def fib(n):
if n==0:
return 0
elif n==1:
return 1
else:
return fib(n-1)+fib(n-2)
# k>=0, k<=n
def bin(n,k):
if k==0 or k==n:
return 1
else:
return bin(n-1,k-1)+bin(n-1,k)
# i>=0, j>=0
def lcs(i,j):
if i==0 or j==0:
return 0
elif x[i]==y[j]:
return lcs(i-1,j-1)+1
else:
return max(lcs(i,j-1),lcs(i-1,j))
# i>=0, u>=0, for all i in 0..n-1 w[i]>0
def knap(i,u):
if i==0 or u==0:
return 0
elif w[i]>u:
return knap(i-1,u)
else:
return max(v[i]+knap(i-1,u-w[i]), knap(i-1,u))
# i>=0, n>=0
def ack(i,n):
if i==0:
return n+1
elif n==0:
return ack(i-1,1)
else:
return ack(i-1,ack(i,n-1))
第4.2.1节本书讨论了确定适当的增量:
1) All possible recursive calls
fact(n) => {n-1}
fib(n) => {fib(n-1), fib(n-2)}
bin(n,k) => {bin(n-1,k-1),bin(n-1,k)}
lcs(i,j) => {lcs(i-1,j-1),lcs(i,j-1),lcs(i-1,j)}
knap(i,u) => {knap(i-1,u),knap(i-1,u-w[i])}
ack(i,n) => {ack(i-1,1),ack(i-1,ack(i,n-1)), ack(i,n-1)}
2) Decrement operation
fact(n) => n-1
fib(n) => n-1
bin(n,k) => [n-1,k]
lcs(i,j) => [i-1,j]
knap(i,u) => [i-1,u]
ack(i,n) => [i,n-1]
3) Minimum increment operation
fact(n) => next(n) = n+1
fib(n) => next(n) = n+1
bin(n,k) => next(n,k) = [n+1,k]
lcs(i,j) => next(i,j) = [i+1,j]
knap(i,u) => next(i,u) = [i+1,u]
ack(i,n) => next(i,n) = [i,n+1]
第4.2.2节讨论了如何形成优化程序:
Recursive
---------
def fExtOpt(x):
if base_cond(x) then fExt0(x ) -- Base case
else let rExt := fExtOpt(prev(x)) in -- Recursion
f Ext’(prev(x),rExt) -- Incremental computation
Iterative
---------
def fExtOpt(x):
if base_cond(x): return fExt0(x) -- Base case
x1 := init_arg; rExt := fExt0(x1) -- Initialization
while x1 != x: -- Iteration
x1 := next(x1); rExt := fExt’(prev(x1),rExt) -- Incremental comp
return rExt
如何在Python中创建{fibExtOpt,binExtOpt,lcsExtOpt,knapExtOpt,ackExtOpt}
?
有关此主题的其他资料可在方法主要作者the papers的Y. Annie Liu, Professor之一找到。
答案 0 :(得分:4)
所以,重申一下这个问题。在我们的案例fac
中,我们有一个函数 f 。
def fac(n):
if n==0:
return 1
else:
return n*fac(n-1)
以递归方式实现。我们希望实现一个函数facOpt
,它可以迭代地执行相同的操作。 fac
几乎以我们需要的形式编写。让我们重写一下:
def fac_(n, r):
return (n+1)*r
def fac(n):
if n==0:
return 1
else:
r = fac(n-1)
return fac_(n-1, r)
这正是4.2节中的递归定义。现在我们需要迭代地重写它:
def facOpt(n):
if n==0:
return 1
x = 1
r = 1
while x != n:
x = x + 1
r = fac_(x-1, r)
return r
这正是4.2节中的迭代定义。请注意facOpt
不会在任何地方调用自己。现在,这既不是最清晰也不是最经典的写下这种算法的方法 - 这只是一种将一种算法转换为另一种算法的方法。我们可以不同地实现相同的算法,例如那样:
def facOpt(n):
r = 1
for x in range(1, n+1):
r *= x
return r
当我们考虑更复杂的功能时,事情变得更有趣。让我们写fibObt
,其中fib
是:
def fib(n):
if n==0:
return 0
elif n==1:
return 1
else:
return fib(n-1) + fib(n-2)
fib
自称两次,但书中的递归模式只允许一次调用。这就是为什么我们需要将函数扩展为返回不是一个而是两个值。完全重新格式化,fib
看起来像这样:
def fibExt_(n, rExt):
return rExt[0] + rExt[1], rExt[0]
def fibExt(n):
if n == 0:
return 0, 0
elif n == 1:
return 1, 0
else:
rExt = fibExt(n-1)
return fibExt_(n-1, rExt)
def fib(n):
return fibExt(n)[0]
您可能会注意到fibExt_
的第一个参数从未使用过。我刚刚添加它以完全遵循建议的结构。
现在,再次将fib
转换为迭代版本也很容易:
def fibExtOpt(n):
if n == 0:
return 0, 0
if n == 1:
return 1, 0
x = 2
rExt = 1, 1
while x != n:
x = x + 1
rExt = fibExt_(x-1, rExt)
return rExt
def fibOpt(n):
return fibExtOpt(n)[0]
同样,新版本不会自行调用。再次,人们可以简化它,例如:
def fibOpt(n):
if n < 2:
return n
a, b = 1, 1
for i in range(n-2):
a, b = b, a+b
return b
转换为迭代版本的下一个功能是bin
:
def bin(n,k):
if k == 0 or k == n:
return 1
else:
return bin(n-1,k-1) + bin(n-1,k)
现在x
和r
都不是数字。索引(x
)有两个组件,缓存(r
)必须更大。一种(不是那么优化)的方式是返回Pascal三角形的整个前一行:
def binExt_(r):
return [a + b for a,b in zip([0] + r, r + [0])]
def binExt(n):
if n == 0:
return [1]
else:
r = binExt(n-1)
return binExt_(r)
def bin(n, k):
return binExt(n)[k]
我没有严格遵循这个模式,并删除了几个无用的变量。仍然可以直接转换为迭代版本:
def binExtOpt(n):
if n == 0:
return [1]
x = 1
r = [1, 1]
while x != n:
r = binExt_(r)
x += 1
return r
def binOpt(n, k):
return binExtOpt(n)[k]
为了完整性,这里是一个优化的解决方案,只缓存行的一部分:
def binExt_(n, k_from, k_to, r):
if k_from == 0 and k_to == n:
return [a + b for a, b in zip([0] + r, r + [0])]
elif k_from == 0:
return [a + b for a, b in zip([0] + r[:-1], r)]
elif k_to == n:
return [a + b for a, b in zip(r, r[1:] + [0])]
else:
return [a + b for a, b in zip(r[:-1], r[1:])]
def binExt(n, k_from, k_to):
if n == 0:
return [1]
else:
r = binExt(n-1, max(0, k_from-1), min(n-1, k_to+1) )
return binExt_(n, k_from, k_to, r)
def bin(n, k):
return binExt(n, k, k)[0]
def binExtOpt(n, k_from, k_to):
if n == 0:
return [1]
ks = [(k_from, k_to)]
for i in range(1,n):
ks.append((max(0, ks[-1][0]-1), min(n-i, ks[-1][1]+1)))
x = 0
r = [1]
while x != n:
x += 1
r = binExt_(x, *ks[n-x], r)
return r
def binOpt(n, k):
return binExtOpt(n, k, k)[0]
最后,最困难的任务不是从递归实现切换到迭代实现,而是要有一个遵循所需模式的递归实现。所以真正的问题是如何创建fibExt'
,而不是fibExtOpt
。