【问题标题】:Recursive to iterative using a systematic method [closed]使用系统方法递归到迭代[关闭]
【发布时间】:2016-11-28 18:54:20
【问题描述】:

几天前我开始阅读这本书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}

有关此主题的其他材料可以在该方法的主要作者Y. Annie Liu, Professorthe papers 之一中找到。

【问题讨论】:

  • 这是关于这个主题的另一个更普遍的问题:stackoverflow.com/questions/159590/…。如果您在反递归某些代码方面需要一些帮助,请将其发布,以便我们更有效地为您提供帮助。
  • 那你问什么?发布一些代码,以便我们为您提供建议。由于帧对象的 cPython 实现,通常在 Python 中递归比正常循环慢。
  • @HolyDanna 我将编辑我的问题以添加我正在使用的一组简单的朴素递归算法,以了解 book 的方法,而不是像上面提到的那样使用自定义堆栈在您的链接中。
  • 在我看来,没有得到认可和没有回应是因为这还不是一个独立的问题。为了比较,我们可以很容易地在网上找到阿克曼函数、二项式函数和其他参考的参考资料。我们不能轻易找到的是第 4.3 节。目前,您的听众仅限于阅读并理解第 4 章并愿意为您提供相关教程的人。教程一般超出了 SO 的范围。
  • 也许这更适合programmers.stackexchange.com

标签: python recursion


【解决方案1】:

所以,重申一下这个问题。我们有一个函数f,在我们的例子中是fac

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 不会在任何地方调用自己。现在,这既不是写下这个算法的最清晰也不是最 Pythonic 的方式——这只是一种将一种算法转换为另一种算法的方式。我们可以不同地实现相同的算法,例如像这样:

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) 必须更大。一种(不太理想)的方法是返回帕斯卡三角形的整个前一行:

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

【讨论】:

  • 感谢您的回答!可能我会给你赏金,因为没有任何其他答案,我不希望它被浪费......无论如何,如果你觉得你可以完成其余功能的答案您还将获得验证。顺便说一句,您是否了解建议的方法? +1 初次尝试。
  • 我找到了online这本书,并且已经阅读了这一章,所以我想我理解了所提出的方法。但是我认为您的问题确实没有提供足够的信息来回答它,而无需先检查这本书。我将扩展我的答案,并可能对问题提出修改建议。
  • 目前赏金是你的......如果你正在编辑问题并完成答案,当然,我也会验证你的答案,谢谢你的努力!
  • 抱歉,刚才看到你的问题了,我看你还没有提供整套问题的解决方案,但是答案已经很完整了,我接受。感谢您的努力!
猜你喜欢
  • 1970-01-01
  • 2015-07-19
  • 2013-11-05
  • 2016-11-10
  • 2021-12-11
相关资源
最近更新 更多