返回第n个Fibonacci数的快速递归函数

时间:2015-09-08 18:49:43

标签: c++ algorithm recursion fibonacci

任何人都可以解释以下代码的工作原理。代码是作为返回第n个Fibonacci数的函数的快速递归实现给出的。我对递归函数的工作原理有一个大概的了解。我可以完全理解这种函数的直接递归实现,使用Fibonacci数的定义,然而,这不是有效的。 我无法掌握的主要问题是当我们在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;
}

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

4 个答案:

答案 0 :(得分:2)

您可能错过了这个函数返回两个结果的事实:一个作为返回值,另一个作为参考传递的“input”参数。

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

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

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

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

编辑(响应您的编辑):

  

我无法掌握的主要内容是fib(n - 1,prev0)的回归   当我们在prev0中存储垃圾时。

技巧是prev0是函数的输出,而不是输入。

  

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

函数签名中int &的参数声明允许函数在选择时使用该参数作为输入或输出或两者。此特定函数将其用作输出。

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

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

答案 1 :(得分:2)

请注意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;
}

答案 2 :(得分:2)

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

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

此函数需要指数时间O(2 n ),因为它会在每一步重新计算先前计算的斐波纳契数。第二个函数需要线性时间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比我做得更好(虽然你应该注意他的惯例,F 0 = 1而不是F 0 = 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 之前非常大。

答案 3 :(得分:0)

找到斐波纳契数的明显(无效)实现将是:

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变大,这将变得更加低效。

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