C语言写一个斐波那契数列

  下午正好有人问到这个问题,所以就写一个。
  斐波那契数列的实现我们可以很快想到——递推,即当前项等于前两项的和。但是这样还是略微令人不愉快,它是 O ( n ) O(n) O(n)的(虽然uint64也只能算到93项…性能差距不明显),我们有没有更快的方法呢?此时就该求助于数学了。
  简单的递归数列可以一般性的表示为
x n + k = λ 1 x n + k − 1 + λ 2 x n + k − 2 + ⋅ ⋅ ⋅ + λ n x n + q x_{n+k}=\lambda_1x_{n+k-1}+\lambda_2x_{n+k-2}+\cdot\cdot\cdot+\lambda_nx_n+q xn+k=λ1xn+k1+λ2xn+k2++λnxn+q  在初始值确定的时候,它被叫做 k k k阶常系数递归数列。而这样的一个k次方程就被称作上式的特征方程:
x k = λ 1 x k − 1 + λ 2 x k − 2 + ⋅ ⋅ ⋅ + λ k − 1 x + λ k x^{k}=\lambda_1x^{k-1}+\lambda_2x^{k-2}+\cdot\cdot\cdot+\lambda_{k-1}x+\lambda_k xk=λ1xk1+λ2xk2++λk1x+λk  对应的斐波那契数列表示为 a n + 2 = a n + 1 + a n , n > 2 a_{n+2}=a_{n+1}+a_{n},n>2 an+2=an+1+an,n>2,初始项为 a 1 = a 2 = 1 a_1=a_2=1 a1=a2=1,是确定的。它的特征方程就是:
x 2 = x + 1 x^2=x+1 x2=x+1  对应的,按照二元一次方程的求根公式,这个方程的根就是: { x 1 = α = 1 + Δ 2 x 2 = β = 1 − Δ 2 \left\{ \begin{aligned} x_1 &=\alpha=\frac{1+\sqrt{\Delta}}{2} \\ x_2 &=\beta=\frac{1-\sqrt{\Delta}}{2} \end{aligned} \right. x1x2=α=21+Δ =β=21Δ   其中的 Δ = 5 \Delta=\sqrt{5} Δ=5 ,很显然,该方程具有两个不相等的实数根。我们设 A , B A,B A,B为待定系数,且满足:
a n = A α n − 1 + B β n − 1 a_n=A\alpha ^{n-1}+B\beta ^{n-1} an=Aαn1+Bβn1  因此:   { a 1 = A + B a 2 = α A + β B \left\{ \begin{aligned} a_1 &=A+B \\ a_2 &=\alpha A + \beta B \end{aligned} \right. {a1a2=A+B=αA+βB  所以得到: { A = a 2 − β a 1 α − β = 1 + 5 2 5 B = − a 2 − α a 1 α − β = − 1 − 5 2 5 \left\{ \begin{aligned} A &=\frac{a_2-\beta a_1}{\alpha - \beta}=\frac{1+\sqrt 5}{2\sqrt 5}\\ B &=-\frac{a_2-\alpha a_1}{\alpha - \beta}=-\frac{1-\sqrt 5}{2\sqrt 5} \end{aligned} \right. AB=αβa2βa1=25 1+5 =αβa2αa1=25 15   这样就能得到 a n a_n an了: a n = A α n − 1 + B β n − 1 = ( 1 + 5 2 5 ) ( 1 + 5 2 ) n − 1 − ( 1 − 5 2 5 ) ( 1 − 5 2 ) n − 1 = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] \begin{aligned} a_n &= A\alpha ^{n-1}+B\beta ^{n-1} \\ &=\left(\frac{1+\sqrt 5}{2\sqrt 5}\right)\left( \frac{1+\sqrt{5}}{2} \right)^{n-1}-\left(\frac{1-\sqrt 5}{2\sqrt 5}\right)\left( \frac{1-\sqrt{5}}{2} \right)^{n-1} \\ &= \frac{1}{\sqrt 5}\left[\left( \frac{1+\sqrt{5}}{2} \right)^n - \left( \frac{1-\sqrt{5}}{2} \right)^n \right] \end{aligned} an=Aαn1+Bβn1=(25 1+5 )(21+5 )n1(25 15 )(215 )n1=5 1[(21+5 )n(215 )n]  这就是我们得到的最终结果: a n = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] a_n= \frac{1}{\sqrt 5}\left[\left( \frac{1+\sqrt{5}}{2} \right)^n - \left( \frac{1-\sqrt{5}}{2} \right)^n \right] an=5 1[(21+5 )n(215 )n]  开始写程序。需要注意的是,因为运算精度的问题,我们需要做一定的舍入处理,即程序里面的 + 0.5。经过测试,这个程序可以计算这个数列的前93项:

#include 
#include 
#include 
typedef unsigned long long int uint64;
uint64 fib(int n)
{
    double r;
    const double q5 = sqrt(5.0);
    assert(n <= 93);                            // 93项后超过uint64
    r = pow((1 + q5) * 0.5, n) - pow((1 - q5) * 0.5, n);
    return (uint64)(r / q5 + 0.5);
}
int main(void)
{
    int i;
    for (i = 1; i <= 93; i++)
    {
        auto t = fib(i);
        printf("a[%2d] = %llu\n", i, fib(i));
    }
    return 0;
}

  针对于文中的推导过程,实际上我们考虑的是 x 2 = p x + q x^2=px+q x2=px+q这个特征方程的两根不等的情况。

你可能感兴趣的:(杂七杂八)