【问题标题】:Fast recursive function for returning n-th Fibonacci number用于返回第 n 个斐波那契数的快速递归函数
【发布时间】:2015-12-04 13:42:15
【问题描述】:

谁能解释下面的代码是如何工作的。该代码是作为返回第 n 个斐波那契数的函数的快速递归实现给出的。我对递归函数的工作原理有一个大致的了解。我可以完全理解这种函数的直接递归实现,使用斐波那契数的定义,但是效率不高。 我无法理解的主要事情是当我们将垃圾存储在 prev0 中时 fib(n – 1, prev0) 会返回什么。

int fib(int n, int &prev1) {
    if (n < 2) {
        prev1 = 0;
        return n;
    }
    int prev0;
    prev1 = fib(n – 1, prev0);
    return prev0 + prev1;
}

我是初学者,所以,请尽可能具体。

【问题讨论】:

  • 为什么你认为代码不应该工作?
  • 您有什么特别不明白的地方?请详细说明您的问题。
  • 这个函数很快,因为它只在一个路径上递归。
  • 代码应该可以工作(这不是我写的),我只是无法掌握它背后的算法。我确实理解一般的想法,它试图保存一些斐波那契数,以进一步使用它们而不是重新计算,就像简单的递归实现一样@CaptainObvlious
  • @Bathsheba 不幸的是,没有。它不会立即返回调用的返回值,而是执行后续添加。

标签: c++ algorithm recursion fibonacci


【解决方案1】:

您可能错过了这个函数返回两个结果的事实:一个作为它的返回值,一个在“输入”参数中,通过引用传递。

fib 的简单递归定义的严重低效率在于,在每个递归级别,您必须对较低级别进行两次不同的调用,即使其中一个包含另一个的所有工作。

通过允许包含“其他”所有工作的那个也返回“其他”的结果,您可以避免在每个级别上加倍工作。

在数学意义上它不再是一个“函数”(因为副作用)。但作为编程意义上的函数,它通过一次调用返回两个值来回避 fib 的效率问题。

我认为值得一提的是,在 C++ 中,有更优雅的方法可以将一对值作为函数的结果返回。 (即使在 C 中,您也可以按值返回结构)。

编辑(响应您的编辑):

我无法理解的主要事情是 fib(n – 1, prev0) 返回什么 当我们有垃圾存储在 prev0 中时。

诀窍在于 prev0 是函数的输出,而不是输入。

我是初学者,所以,请尽可能具体。

函数签名中int &amp; 的参数声明允许函数使用该参数作为输入或输出,或两者兼而有之。这个特殊的函数使用它作为输出。

如果您了解递归函数的基础知识,您就会了解每个级别的递归如何拥有自己的参数 n 和局部变量 prev0 的副本。但是 prev1 不是一个单独的变量。它实际上是更高级别 prev0 的别名。因此,当前级别的 prev1 的任何读取或写入实际上都发生在更高级别的 prev0 上。

此级别的 n 是“按值”传递的,这意味着它是所传递表达式值的副本(更高级别的 n-1 )。但是这个级别的prev1是通过引用传递的,所以它不是上级prev0的值的副本,而是上级prev0的别名。

【讨论】:

  • 加一个,但我认为您应该提及尾递归以获得完整答案。
  • 非常感谢您的回答,这以某种方式为我清除了一些东西(我不知道该函数还返回 prev1)。请您更具体地解释一下它在哪里获得价值并获得回报。我不能完全掌握那部分。
  • 我们有函数声明int fib(int n, int &amp;prev1),其中&amp; 导致函数内部对prev1 所做的任何更改都会发生在作为prev1 传递的任何变量上。然后我们在函数prev1 = fib(n – 1, prev0); 中首次使用了prev1,它将其设置为递归调用的主要结果。在同一行中,我们看到 prev0 “传递给” prev1(但信息流是另一种方式),因此存储在被调用函数的 prev1 中的值进入该函数的 prev0。
【解决方案2】:

寻找斐波那契数的明显(非高效)实现是:

int fib(int n) {
    if (n<2) return n;
    return fib(n-2) + fib(n-1);
}

此实现效率低下,您需要执行两次相同的计算。

例如,如果 n 为 6,您的算法会告诉您添加 fib(4) 和 fib(5)。要找到 fib(5),需要添加 fib(4) 和 fib(3)。然后你就可以第二次计算 fib(4) 了。随着 n 变大,这将变得更加低效。

您提供的示例通过记住前面的斐波那契数列来避免这种低效率。

【讨论】:

    【解决方案3】:

    请注意,prev1 永远不会读取。仅写入。让我们这样考虑函数:

    std::pair<int,int> fib_pair(int n) {
        if (n < 2) {
            return std::make_pair(n, 0);
        }
    
        std::pair<int, int> prev = fib_pair(n-1);
        return std::make_pair(prev.first + prev.second, prev.first);
    }
    

    现在更清楚了 - 有一个递归调用,fib(n) 返回 两个 以前的数字,而不仅仅是一个。结果,我们有一个线性函数而不是指数函数。然后我们可以根据这个重写原始版本以帮助我们理解两者:

    int fib(int n, int &prev1) {
        std::pair<int, int> pair = fib_pair(n);
        prev1 = pair.second;
        return pair.first;
    }
    

    【讨论】:

    • 我希望你在哪里写 return prev.first + prev.second; 你的意思是 return std::make_pair( prev.first + prev.second, prev.first); 所以你返回 fib(n) 和 fib(n-1),使用递归调用返回 fib(n- 1) 和 fib(n-2)
    • @JSF 是的,你是对的。我只是复制了整个函数,并没有改变那行。
    【解决方案4】:

    让我们看一下计算第 n 个斐波那契数的四个不同函数,它们使用伪代码而不是将程序限制为单一语言。第一个遵循标准的递归定义:

    function fib(n) # exponential
        if n <= 2 return 1
        return fib(n-1) + fib(n-2)
    

    此函数需要指数时间,O(2n),因为它在每一步都重新计算先前计算的斐波那契数。第二个函数需要线性时间 O(n),通过从 1 到 n 而不是 n 到 1 并跟踪两者以前的斐波那契数:

    function fib(n) # linear
        if n <= 2 return 1
        prev2 = prev1 = 1
        k := 3
        while k <= n
            fib := prev2 + prev1
            prev2 := prev1
            prev1 := fib
        return fib
    

    这与您的程序使用的算法相同,尽管您的程序通过递归操作并通过指向外部范围内的变量的指针传递参数之一来掩盖正在发生的事情。

    Dijkstra described 一种算法,用于在对数时间内计算 n 斐波那契数,O(log n),使用矩阵和平方算法求幂。我不会在这里给出完整的解释; Dijkstra 比我做得更好(尽管您应该注意他的约定 F0 = 1 而不是 F0 = 0,因为我们一直在这样做)。算法如下:

    function fib(n) # logarithmic
        if n <= 2 return 1
        n2 := n // 2 # integer division
        if n % 2 == 1 return square(fib(n2+1)) + square(fib(n2))
        return fib(n2) * (2*fib(n2-1) + fib(n2))
    

    第四个算法在恒定时间 O(1) 中运行,前提是您有足够精度的浮点数,使用斐波那契数的数学定义:

    function fib(n) # constant
        sqrt5 := sqrt(5)
        p := (1 + sqrt5) / 2
        q := 1 / p
        return floor((p**n + q**n) / sqrt5 + 0.5)
    

    对于大多数语言,最后一种算法不是很有用,因为对于任何大小的斐波那契数,您都需要某种无限制精度的十进制算术库,尽管它是恒定时间,但在实践中可能比简单的对数需要更长的时间-time 算法在无限精度整数上运行,至少在 n 非常大之前。

    【讨论】:

      猜你喜欢
      • 2012-11-29
      • 2012-10-12
      • 2021-07-18
      • 2013-11-13
      • 1970-01-01
      • 1970-01-01
      • 2022-12-10
      • 2020-03-21
      • 2021-12-01
      相关资源
      最近更新 更多