在使用递归来计算斐波那契数列的第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来运行该程序。这是一个四核系统。
答案 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)
long long unsigned int
也不能包含Fibonacci值
100号(甚至接近它)建议使用一个非常简单的程序来启动,计算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 停止程序。