C 为什么递归要花这么长时间?
在使用递归计算斐波那契序列的第n个数时,我编写了以下简单程序:C 为什么递归要花这么长时间?,c,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
#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);
}
}
#包括
#包括
#包括
无符号整数长fibonacci(无符号整数);
//掷骰子游戏
int main(int argc,字符**argv)
{
对于(int n=1;n如果您有单线程程序,则不会利用四核系统
它将只在一个内核上运行,因此21/25%的CPU使用率是现实的
使用它的一种方法是,首先不要使用递归,因为它会让人讨厌。当你有一个for/while循环时,将它拆分为4个while循环,并将每个while循环放在一个新线程中。然后你必须管理同步以正确打印消息,但这甚至没有那么难。你可以将所有结果存储在一个线程中完成所有线程后,请重新排列并打印它
对递归函数的调用次数大约为2^n,即指数复杂度。明白
我不确定你是否完全理解这一点,因为你似乎对它在n=40和n=47左右变慢感到惊讶
复杂度为2^n,n为40,即240,或109951162776,或约1万亿次运算。如果您的计算机每纳秒能运行其中一次运算,即每秒10亿次运算,则需要1000秒才能完成
考虑一下,如果n仅为30,则230为1073741824,在同一台计算机上只需约1秒
如前所述,您只使用了一个内核。您可以并行化,但这不会有多大帮助。使用四个内核而不是一个,我的n=40示例仍需要250秒。上升到n=42,您将返回到1000秒,因为并行化最多只能使您的性能成倍增长,但这样的算法会成倍增长
发布的代码包含一些极端的过度复杂性
即使是long无符号int
也不能包含斐波那契值
数字100(甚至接近它)
建议使用一个非常简单的程序开始,一个计算斐波那契序列的程序。然后使用该程序确定如何显示结果
下面的程序计算数字非常快,但仍然存在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;
}
}
那么,为什么递归要花这么长时间呢
因为有大量的递归和溢出的处理
上面建议的代码消除了递归,但没有溢出,并且(在我的计算机上)运行不到一秒钟。根据@给出的答案,您可以使用提供的无限精度算术库消除他提到的溢出
例如
#包括//printf
#包括//免费
#包括//mpz\t
内部主(空)
{
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(计数器,1);
printf(“0:0\n”);
而(1){
char*tempNumRepr=mpz_get_str(NULL,10,tempNum);
char*counterRepr=mpz\u get\u str(NULL,10,计数器);
printf(“%s:%s\n”,计数器repr,tempNumRepr);
免费(tempNumRepr);
免费(反报告);
mpz_add(tempNum,currNum,prevNum);//tempNum=currNum+prevNum;
mpz_add_ui(计数器,计数器,1);//计数器=计数器+1;
mpz_集(prevNum,currNum);//prevNum=currNum;
mpz_集(currNum,tempNum);//currNum=tempNum;
}
mpz_清除(prevNum);
mpz_清除(currNum);
mpz_清除(tempNum);
mpz_清除(计数器);
返回退出成功;
};
要编译它,请确保已安装libgmp,键入:
~$ gcc fib.c -lgmp
你很快就能得到大量的斐波那契值:
~$ ./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停止程序。21%与25%相差不远,也就是说,您可以使用的一个核心的100%。如果问题是为什么需要这么长时间,那是因为它必须计算前面的每个数字。这需要很长时间,因为这是一种方法,虽然简单,但可以说是效率最低的方法(双重递归)做斐波那契序列计算。它的递归量随着数字的大小呈指数增长,并且它会多次重新计算值。谢谢各位。我知道这种方法效率不高;我只是好奇为什么它没有使用所有的内核。我想现在我必须研究如何在c中分配新线程。实际上,just查找记忆。良好的编程胜过更快的硬件。溢出肯定会妨碍正确性,但从复杂性的角度来看,我不认为它会使计算速度减慢。无论如何,+1感谢使用gmp库的示例
~$ ./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
...