为什么递归需要这么长时间?

时间:2017-09-06 17:37:15

标签: c

在使用递归来计算斐波那契数列的第n个数时,我写了这个简单的程序:

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

unsigned int long long fibonacci(unsigned int number);

//game of craps
int main(int argc, char** argv)
{

    for(int n = 1; n <= 100; n++)
    {
        printf("%llu\n", fibonacci(n));
    }

    return (EXIT_SUCCESS);
}


unsigned long long int fibonacci(unsigned int number)
{
    if (number == 0 || number == 1)
    {
        return number;
    }
    else
    {
        return fibonacci(number - 2) + fibonacci(number - 1);      
    }
}

其中对序列中n + 1个数的每次调用都会使程序必须运行的函数调用次数加倍。因此,对递归函数的调用次数是2 ^ n或指数复杂度。了解。但是,所有的计算能力在哪里?一旦序列中的第n个数字开始达到40,计算机开始花费显着的时间来计算结果,其中在n = 47时它花费30+秒。但是我的计算机显示我只使用了21%的CPU功率。我正在使用NetBeans IDE来运行该程序。这是一个四核系统。

4 个答案:

答案 0 :(得分:4)

  

对递归函数的调用次数是2 ^ n或指数复杂度。理解。

我不确定你是否完全理解这一点,因为你似乎对n = 40和n = 47附近的速度感到惊讶。

复杂度为2 ^ n, n 为40,即2 40 ,或1,099,511,627,776,或约1 运营。如果您的计算机每纳秒运行一次这样的操作,即每秒10亿次操作,则需要1000秒才能完成。

考虑 n 是否只有30. 2 30 是1,073,741,824,在同一台计算机上只需要1秒左右。

如前所述,您只使用一个核心。您可以并行化,但这无济于事。使用四个核心而不是一个,我的n = 40示例仍然需要250秒。上升到n = 42并且你回到1000秒,因为并行化最好会增加你的性能,但是像这样的算法会呈指数级增长。

答案 1 :(得分:2)

  1. 发布的代码包含一些极端复杂性。
  2. 即使long long unsigned int也不能包含Fibonacci值 100号(甚至接近它)
  3. 建议使用一个非常简单的程序来启动,计算Fibonacci序列。然后使用该程序确定如何显示结果。

    以下程序计算数字,速度非常快,但仍存在long long unsigned int溢出的问题

    #include <stdio.h>  // printf()
    
    
    int main( void )
    {
        long long unsigned currentNum = 1;
        long long unsigned priorNum = 1;
    
        printf( "1\n1\n" );
    
        for (size_t i = 2;  i < 100; i++ )
        {
            long long unsigned newNum = currentNum+priorNum;
            printf( "%llu\n", newNum );
            priorNum = currentNum;
            currentNum = newNum;
        }
    }
    

    在我的linux 86-64计算机上,这是输出的最后几行,显示溢出问题。

    99194853094755497
    160500643816367088
    259695496911122585
    420196140727489673
    679891637638612258
    1100087778366101931
    1779979416004714189
    2880067194370816120
    4660046610375530309
    7540113804746346429
    12200160415121876738
    1293530146158671551
    13493690561280548289
    14787220707439219840
    9834167195010216513
    6174643828739884737
    16008811023750101250
    3736710778780434371
    

    那么,为什么递归需要这么长时间?

    因为大量的递归和溢出的处理

    上面建议的代码消除了递归,但没有溢出,并且运行时间不到一秒(在我的计算机上)。

答案 2 :(得分:1)

如果您有单线程程序,则不会使用四核系统。

它只能在一个核心上运行,因此21/25%的CPU使用率是切合实际的。

使用它的一种方法是,首先不使用递归,因为它使得它很烦人,并且当你有一个for / while循环时将它分成4个while循环并将它们中的每一个放在一个新线程中。然后你必须管理同步才能正确打印消息,但它甚至不是那么难。您可以将所有结果存储在一个数组中,然后在完成所有线程后打印它。

答案 3 :(得分:0)

在@ user3629249的答案的基础上,您可以使用{{提供的无限精度算术库来摆脱他提到的溢出 3}}

<强> e.g。

#include <stdio.h>  // printf
#include <stdlib.h> // free
#include <gmp.h>    // mpz_t

int main( void )
{
    mpz_t prevNum, currNum, tempNum, counter;

    mpz_init_set_si(prevNum, 0);
    mpz_init_set_si(currNum, 1);
    mpz_init_set_si(tempNum, 1);
    mpz_init_set_si(counter, 1);

    printf( "0: 0\n" );

    while (1) {
        char *tempNumRepr = mpz_get_str(NULL, 10, tempNum);
        char *counterRepr = mpz_get_str(NULL, 10, counter);

        printf("%s: %s\n", counterRepr, tempNumRepr);

        free(tempNumRepr);
        free(counterRepr);

        mpz_add(tempNum, currNum, prevNum); // tempNum = currNum + prevNum;
        mpz_add_ui(counter, counter, 1);    // counter = counter + 1;
        mpz_set(prevNum, currNum);          // prevNum = currNum;
        mpz_set(currNum, tempNum);          // currNum = tempNum;
    }

    mpz_clear(prevNum);
    mpz_clear(currNum);
    mpz_clear(tempNum);
    mpz_clear(counter);

    return EXIT_SUCCESS;
};

要编译它,请确保已安装 libgmp ,键入:

~$ gcc fib.c -lgmp

您可以非常快速地获得大量 fibonacci 值:

~$ ./a.out
0: 0
1: 1
2: 1
3: 2
4: 3
5: 5
6: 8
7: 13
8: 21
9: 34
...
90: 2880067194370816120
91: 4660046610375530309
92: 7540113804746346429
93: 12200160415121876738
94: 19740274219868223167
95: 31940434634990099905
96: 51680708854858323072
97: 83621143489848422977
98: 135301852344706746049
99: 218922995834555169026
100: 354224848179261915075
...
142: 212207101440105399533740733471
143: 343358302784187294870275058337
144: 555565404224292694404015791808
145: 898923707008479989274290850145
146: 1454489111232772683678306641953
147: 2353412818241252672952597492098
148: 3807901929474025356630904134051
149: 6161314747715278029583501626149
150: 9969216677189303386214405760200
...
10456: 6687771891046976665010914682715972428661740561209776353485935351631179302708216108795962659308263419533746676628535531789045787219342206829688433844719175383255599341828410480942962469553971997586487609675800755252584139702413749597015823849849046700521430415467867019518212926720410106893075072562394664597041033593563521410003073230903292197734713471051090595503533547412747118747787351929732433449493727418908972479566909080954709569018619548197645271462668017096925677064951824250666293199593131718849011440475925874263429880250725807157443918222920142864819346465587051597207982477956741428300547495546275347374411309127960079792636429623948756731669388275421014167909883947268371246535572766045766175917299574719971717954980856956555916099403979976768699108922030154574061373884317374443228652666763423361895311742060974910298682465051864682016439317005971937944787596597197162234588349001773183227535867183191706435572614767923270023480287832648770215573899455920695896713514952891911913499762717737021116746179317675622780792638129991728650763618970292905899648572351513919065201266611540504973510404007895858009291738402611754822294670524761118059571137973416151185102238975390542996959456114838498320921216851752236455715812273599551395186676228882752252829522673168259864505917922994675966393982705428427387550834530918600733123354437191268657802903434440996622861582962869292133202292740984119730918997492224957849300327645752441866958526558379656521799598935096546592129670888574358354955519855060127168291877171959996776081517513455753528959306416265886428706197994064431298142841481516239689015446304286858347321708226391039390175388745315544138793021359869227432464706950061238138314080606377506673283324908921190615421862717588664540813607678946107283312579595718137450873566434040358736923152893920579043838335105796035360841757227288861017982575677839192583578548045589322945
...

使用 CTRL + C 停止程序。