使用系统方法递归迭代

时间:2016-07-25 12:20:48

标签: python recursion

我几天前开始阅读 Systematic Program Design: From Clarity to Efficiency 这本书。第4章讨论了将任何递归算法转换为其对应迭代的系统方法。这似乎是一种非常强大的通用方法,但我很难理解它是如何工作的。

在阅读了一些关于使用自定义堆栈去除递归的文章之后,感觉这个提出的方法会产生更加可读,优化和紧凑的输出。

Python中的递归算法,我想应用方法

#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))

Step Iterate:确定最小增量,将递归转换为迭代

第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 papersY. Annie Liu, Professor之一找到。

1 个答案:

答案 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)

现在xr都不是数字。索引(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