斐波那契数列 求解

问题起源

兔子生长的数目问题:

第一个月初有一对刚诞生的兔子
第二个月之后(第三个月初)它们可以生育
每月每对可生育的兔子会诞生下一对新兔子
兔子永不死去

如果笔算, 前几项可以很快得出:

n n n0123456789
F n F_n Fn0112358132134

数学描述

  • 递推公式
    F 0 = 0 F 1 = 1 F n = F n − 1 + F n − 2 \begin{aligned} F_0 &= 0 \\ F_1 &= 1 \\ F_n &= F_{n-1} + F_{n-2} \\ \end{aligned} F0F1Fn=0=1=Fn1+Fn2

  • 通项公式
    F n = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] , n ≥ 0 F_n = \dfrac{1}{\sqrt{5}} [(\dfrac{1 + \sqrt{5}}{2})^{n} – (\dfrac{1 – \sqrt{5}}{2})^{n}], n \ge 0 Fn=5 1[(21+5 )n(215 )n],n0

  • 通项公式推导
    参见: 利用特征方程

  • 其他诸多特性参见:https://baike.baidu.com/item/斐波那契数列/99145

代码实现

注意:当 n < 0 时, 对应现实中的兔子问题,返回0.
代码实现有递归、迭代、利用通项等方式, 分别给出:

  • 直接递归(根据定义)

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

    直接递归,当n太大时会造成栈溢出

  • 尾递归
    关于尾递归可以参看: 尾调用优化

    int fib_recursive_tail(int n, int a, int b) {
        if (n < 0) {
            return 0;
        }
        if (n == 0) {
            return a;
        }
        if (n == 1) {
            return b;
        }
        return fib_recursive_tail(n - 1, b, a + b);
    }
    
  • 借用数组迭代
    arr[i] = arr[i – 1] + arr[i – 2]

    int fib_iteration_arr(int n) {
        if (n < 0) {
            return 0;
        }
        if (n < 2) {
            return n;
        }
        int* arr = (int*)malloc((n + 1) * sizeof(int));
        arr[0] = 0;
        arr[1] = 1;
        int i;
        int ret = 0;
    
        for (i = 2; i <= n; i++) {
            arr[i] = arr[i - 1] + arr[i - 2];
        }
    
        ret = arr[n];
        free(arr);
        return ret;
    }
    

    利用数组迭代, 需要额外的内存空间(如上述代码中的malloc向堆中申请了内存);
    事实上, 我们需要的只是a[n], 所以可以考虑临时变量来实现;

  • 借用临时变量迭代

    int fib_iteration(int n) {
        if (n < 0) {
            return 0;
        }
        if (n < 2) {
            return n;
        }
        int a = 0, b = 1;
        int t;
        while (n--) {
            t = b;
            b = a + b;
            a = t;
        }
        return a;
    }
    
  • 利用通项公式

    #include <math.h>
    
    int fib_formula(int n) {
        if (n < 0) {
            return 0;
        }
        const double sqrt_5 = sqrt(5);
    
        return round(
        	(pow((1 + sqrt_5) / 2, n) - pow((1 - sqrt_5) / 2, n)) / sqrt_5
        );
    }
    

    利用通项公式时,要利用几个库函数(sqrt, pow, round)。缓存了 5 \sqrt{5} 5 的值, 避免多次计算。

参考

欢迎补充指正!

点赞