【问题标题】:Any way to make recursive functions faster?有什么方法可以使递归函数更快?
【发布时间】:2017-10-27 07:36:49
【问题描述】:

在对递归函数进行一些研究后,我面临着矛盾:一方面以递归方式解决问题很优雅,而另一方面在实践中性能似乎很糟糕,递归调用的数量有限。

我知道默认情况下 Python 的递归深度限制为 1000,但是即使在一个简单的应用程序中,我在 40 到 50 次调用时性能也很差。

我举个例子:

def fibonacci(n):
    if n == 1 or n == 0:
        return 1
    else:
        return fibonacci(n-1) + fibonacci(n-2)

我 PC 上的这个简单递归函数即使对于低 n 也需要大量时间来求解。为了测试,我还写了另一个函数:

def fib_nonrecursive(n):
    fib_seq = [1, 1]
    for i in range(2, n+1):
        value = fib_seq[i-1] + fib_seq[i-2]
        fib_seq.append(value)        
    return fib_seq[i]

即使在大数字上,非递归方式也非常快,因此问题肯定不是涉及的操作或数字的大小。所以我的问题是为什么递归方式这么慢,有没有办法让它更快?有没有办法扩大递归深度?

编辑 由于答案建议使用记忆化,因此我对其进行了研究并在我的示例中实现了它:

def mem(f):
    memory = {}
    def inner_function(x):
        if x not in memory:            
            memory[x] = f(x)
            return memory[x]
        else:
            return memory[x]
    return inner_function

@mem
def fibonacci(n):
    if n == 1 or n == 0:
        return 1
    else:
        return fibonacci(n-1) + fibonacci(n-2)

相同的mem(f) 可以与其他递归函数f 一起使用。必须包含 @mem 部分,以便将 f 作为参数传递给 mem()(请参阅“装饰器”) 这是一种稍微先进的编码方式,但我没有找到更简单的方法是为给定的示例实现记忆。如果有更简单的实现方式请纠正我。

【问题讨论】:

  • 递归 fib 函数可能明显更糟,因为每次“迭代”都会对自身产生 2 次以上的调用。如果我没记错的话,那就是 O(n*2) vs O(n)。您是否发现其他递归函数比它们的迭代版本慢?
  • 阅读记忆化。
  • 递归斐波那契计算非常适合记忆。 stackoverflow.com/questions/1988804/…
  • docs.python.org/3/library/functools.html#functools.lru_cache 可能有助于记忆化。并且:您的问题是关于一般递归还是斐波那契?
  • 如果正在考虑该选项,但无论如何都应该将两个调用都存储在堆栈中,并且我记得 O(n) = O(n*2) 因为两者都是线性的。我经常注意到使用递归的速度很慢,但没有进行并排比较,因为我通常编写一个或另一个版本的代码。

标签: python recursion


【解决方案1】:

忽略 fibonacci() 是记忆化的教科书案例(这将使其更快)这一事实,“深度和廉价”递归在普通 Python 中根本不是一回事。

在许多语言中都有尾调用消除。 Python没有这个。在许多语言中,推送一个额外的堆栈帧非常便宜。在 Python 中并非如此。

要找到存在问题的实际代码并不容易,这可能有助于解释为什么 Python 人员一直保持简单并始终创建具有完全调试能力的真正堆栈帧。在大多数 Python 应用程序中,对廉价和深度递归的需求并不多。

【讨论】:

  • 记忆化也可以称为散列吗?在我看来,这个概念非常相似。所以基本上如果递归是我发现解决问题的唯一方法,我应该尽可能使用记忆或用其他语言编写?
  • 记忆化只是将函数的参数与根据参数评估函数的结果相关联。散列是实现它的一种方法。
【解决方案2】:

递归函数的问题是你用相同的参数调用相同的方法一定次数。例如,在fibrecursive(7) 中,fibrecursive(2) 被调用了 4 次。每次你重做同样的事情。

您可以使用dynamic programming 提高性能。简而言之,您将结果存储在一个数组中,当您调用 fibrecursive(2) 时,您会检查您的数组是否已经存在。

这是文章中的伪代码:

function fib(n)
    if key n is not in map m 
        m[n] := fib(n − 1) + fib(n − 2)
    return m[n]

【讨论】:

    猜你喜欢
    • 2020-06-02
    • 2012-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多