C 斐波那契计算时间

C 斐波那契计算时间,c,loops,for-loop,recursion,fibonacci,C,Loops,For Loop,Recursion,Fibonacci,递归风格的斐波那契与循环风格的斐波那契之间是否存在明显的计算时间差异?我使用递归将斐波那契函数运行到40个位置,然后直接使用循环。计算时间差似乎只是学术性的 用C写的 递归解决方案: int main(int argc, const char * argv[]) { int n, i = 0, c; printf("Please enter an integer:\n"); scanf("%d", &n); for ( c = 1 ; c <

递归风格的斐波那契与循环风格的斐波那契之间是否存在明显的计算时间差异?我使用递归将斐波那契函数运行到40个位置,然后直接使用循环。计算时间差似乎只是学术性的

用C写的

递归解决方案:

    int main(int argc, const char * argv[]) {
    int n, i = 0, c;
    printf("Please enter an integer:\n");
    scanf("%d", &n);
    for ( c = 1 ; c <= n ; c++ )
    {
        printf("%lu ", fibonacci(i));
        i++;
    }
    return 0;
    }

long fibonacci(long n)
{
    if ( n == 0 )
        return 0;
    else if ( n == 1 )
        return 1;
    else
        return ( fibonacci(n-1) + fibonacci(n-2) );
};
int main(int argc, const char * argv[]) {
int n, first = 0, second = 1, next, c;
    printf("Please enter an integer:\n");
    scanf("%d", &n);
    for ( c = 0 ; c < n ; c++ )
    {
        if ( c <= 1 )
            next = c;
        else
        {
            next = first + second;
            first = second;
            second = next;
        }
        printf("%d ",next);
    }
    return 0;
};
int main(int argc,const char*argv[]{
int n,i=0,c;
printf(“请输入一个整数:\n”);
scanf(“%d”和“&n”);

for(c=1;cfor循环解算更快。原因:

  • 没有函数调用(假设函数调用很昂贵)
  • 计算
    n
    th使用
    n
    加法(循环迭代
    n
    次),而递归解决方案使用每个函数调用的加法,它与调用相加,因此
    O(1.6
    additions。代价来自双重递归调用-当递归函数要求
    n
    th元素时,它必须从一开始就重新计算
    n-1
    th和
    n-2
    th元素,它不记得它们

  • for循环不一定更快。在Java、C和Python等通用语言中,递归与迭代相比成本相当高,因为它需要分配新的堆栈框架

    在C/C++中可以消除这种开销,使编译器优化能够执行尾部递归,这将转换某些类型的递归(实际上是某些类型的尾部调用)跳转而不是函数调用。为了让编译器执行此优化,函数在返回之前必须做的最后一件事是调用另一个函数(在本例中为函数本身)

    斐波那契函数的示例如下:

    int fib_tail(int n, int res, int next) 
    {
      if (n == 0) {
        return res;
      }
      return fib_tail(n - 1, next, res + next);
    } 
    
    在汇编级别,启用编译器优化,它将作为循环实现,例如在调用之间共享相同的堆栈框架

    我最近写了一篇关于它的文章


    希望有帮助。

    与尾部递归和迭代版本相比,传统的递归方法速度非常慢。在下面的迭代版本示例代码中,使用展开的循环以及进入循环。对于32位无符号整数,限制为fib(47),对于64位无符号整数,限制为fib(93)

    使用英特尔2600K 3.4ghz、XP X64、64位模式完成计时。XP或XP X64高性能计数器频率与cpu时钟3.4ghz相同,但如果持续时间较短,则操作系统开销(如中断)会影响计时

    fib(40)的计时:

    94个环路的定时,n=0到93:

    fibt() # of microseconds         7
    fibi() # of microseconds         5
    
    示例代码:

    typedef unsigned long long UI64;
    
    UI64 fibr(UI64 n)
    {
        if(n < 2)
            return n;
        return fibr(n-1) + fibr(n-2);
    }
    
    // call with fibt(n, 0, 1)
    UI64 fibt(UI64 n, UI64 res, UI64 next)
    {
        if (n == 0)
            return res;
        return fibt(n - 1, next, res + next);
    }
    
    UI64 fibi(UI64 n)
    {
    UI64 f0, f1, i;
        if(n < 2)
            return n;
        n -= 2;
        f1 = f0 = 1;
        i = 0;
        switch(n%8){
            do{
                f1 += f0;
              case 7:
                f0 += f1;
              case 6:
                f1 += f0;
              case 5:
                f0 += f1;
              case 4:
                f1 += f0;
              case 3:
                f0 += f1;
              case 2:
                f1 += f0;
              case 1:
                f0 += f1;
              case 0:
                continue;
            }while(n >= (i += 8));
        }
        return f0;
    }
    

    你是如何测量速度差的

    Fibonacci函数的简单递归实现需要大约1亿次函数调用来计算f(40)。在现代计算机上,它的速度足够快,以至于你无法用秒表计时

    计算f(50)需要大约100亿次函数调用,这将是一个明显的延迟。f(60)需要超过1万亿次函数调用,或大约一个小时。f(70)需要大约200万亿次函数调用,或几天。f(80)需要大约20万亿次函数调用,或大约一年


    我不认为这种差异是学术性的。

    也许下面的方法会花更少的时间? 你可以编写一个生成斐波那契数列的代码,避免打印0和1的if-else语句,并避免在循环外打印它们。你可以通过使用-1和1初始化“first”和“second”变量来实现这一点,因此它们之间的和将给你0,这是数列的第一个器官,循环将完成剩下的部分电子作业

    #include <iostream>
    
    using namespace std;
    int main()
    {
        int i, num, a = -1, b = 1, temp;
        cout << "enter a number:" << endl;
        cin >> num;
        for ( i = 0 ; i < num + 1 ; i++ ) 
        {
            cout << a + b << " "; 
            temp = a + b; 
            a = b;
            b = temp;
        }
        cout << endl;
        return 0;
    }
    
    #包括
    使用名称空间std;
    int main()
    {
    inti,num,a=-1,b=1,temp;
    库特数;
    对于(i=0;icout看起来您已经在第一段中回答了自己的问题。单个计算的运行时间不会太长。如果要进行基准测试,您需要计算多个(10^7?)泰晤士报。很有意思。最近我对斐波那契序列非常感兴趣。它在自然界中如此多的表现形式中是如何被发现的,它是如何美丽,以及这个序列是如何在基准测试机器中如此频繁地被使用。“40个位置”?-一个64位无符号整数只包含19位数字。一个64位无符号整数的最大n值是93。我的意思是,斐波那契序列的40个索引。从数学上讲,你的描述是有意义的。很明显,递归的增长速度比循环慢。然而,当谈到编译时,如另一个答案所述,进一步的操作优化似乎已经完成了。没错。但主要的问题是要理解方法之间的时间复杂度有很大差异,它们花费几乎相同时间的原因实际上是因为编译器使这些情况几乎相同。不是2^n次调用。大约是1.608^n次调用。谢谢你的解释。很好o知道编译器将完成所有优化,并在时间复杂度上产生更小的差异。但是,从数学上讲,另一个响应也是正确的。您的答案在实际情况中更为有效,因为它非常重要。不客气。不过,请非常小心。C/C++编译器不保证尾部递归,因此您可以始终必须检查它是否启用(即查看部件)你可能遇到的最坏问题是使用一个递归函数,当你使用C或C++编译器来执行Fibonacci函数的尾部递归时,它没有优化,只是好奇。我确信Clang不这样做。这不回答OP的问题,而且可能会让他迷惑。r、 OP错误地认为,在使用n=40进行测试后,naive recursive与“实际场景”中的迭代DP方法具有同等的复杂性。这就是现在的确凿证据。非常感谢您将这一令人惊叹且美丽的序列用于测试
    
    UI64 fibi(UI64 n)
    {
    UI64 f0, f1, i;
        f0 = n & 1;         // if n even, f0=0, f1=1
        f1 = 1 - f0;        // else       f1=0, f0=1
        i = 0;
        switch(n%8){
            do{
                f1 += f0;
              case 7:
                f0 += f1;
              case 6:
                f1 += f0;
              case 5:
                f0 += f1;
              case 4:
                f1 += f0;
              case 3:
                f0 += f1;
              case 2:
                f1 += f0;
              case 1:
                f0 += f1;
              case 0:
                continue;
            }while(n >= (i += 8));
        }
        return f0;
    }
    
    #include <iostream>
    
    using namespace std;
    int main()
    {
        int i, num, a = -1, b = 1, temp;
        cout << "enter a number:" << endl;
        cin >> num;
        for ( i = 0 ; i < num + 1 ; i++ ) 
        {
            cout << a + b << " "; 
            temp = a + b; 
            a = b;
            b = temp;
        }
        cout << endl;
        return 0;
    }