小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

漫談遞歸:從斐波那契開(kāi)始了解尾遞歸

 herowuking 2015-10-10

尾遞歸(tail recursive),看名字就知道是某種形式的遞歸。簡(jiǎn)單的說(shuō)遞歸就是函數(shù)自己調(diào)用自己。那尾遞歸和遞歸之間的差別就只能體現(xiàn)在參數(shù)上了。

尾遞歸wiki解釋如下:

尾部遞歸是一種編程技巧。遞歸函數(shù)是指一些會(huì)在函數(shù)內(nèi)調(diào)用自己的函數(shù),如果在遞歸函數(shù)中,遞歸調(diào)用返回的結(jié)果總被直接返回,則稱為尾部遞歸。尾部遞歸的函數(shù)有助將算法轉(zhuǎn)化成函數(shù)編程語(yǔ)言,而且從編譯器角度來(lái)說(shuō),亦容易優(yōu)化成為普通循環(huán)。這是因?yàn)閺碾娔X的基本面來(lái)說(shuō),所有的循環(huán)都是利用重復(fù)移跳到代碼的開(kāi)頭來(lái)實(shí)現(xiàn)的。如果有尾部歸遞,就只需要疊套一個(gè)堆棧,因?yàn)殡娔X只需要將函數(shù)的參數(shù)改變?cè)僦匦抡{(diào)用一次。利用尾部遞歸最主要的目的是要優(yōu)化,例如在Scheme語(yǔ)言中,明確規(guī)定必須針對(duì)尾部遞歸作優(yōu)化。可見(jiàn)尾部遞歸的作用,是非常依賴于具體實(shí)現(xiàn)的。

我們還是從簡(jiǎn)單的斐波那契開(kāi)始了解尾遞歸吧。

用普通的遞歸計(jì)算Fibonacci數(shù)列:

01#include "stdio.h"
02#include "math.h"
03 
04int factorial(int n);
05 
06int main(void)
07{
08    int i, n, rs;
09 
10    printf("請(qǐng)輸入斐波那契數(shù)n:");
11    scanf("%d",&n);
12 
13    rs = factorial(n);
14    printf("%d \n", rs);
15 
16    return 0;
17}
18 
19// 遞歸
20int factorial(int n)
21{
22    if(n <= 2)
23    {
24        return 1;
25    }
26    else
27    {
28        return factorial(n-1) + factorial(n-2);
29    }
30}

程序員運(yùn)行結(jié)果如下:

1請(qǐng)輸入斐波那契數(shù)n:20
26765
3 
4Process returned 0 (0x0)   execution time : 3.502 s
5Press any key to continue.

在i5的CPU下也要花費(fèi) 3.502 秒的時(shí)間。

下面我們看看如何用尾遞歸實(shí)現(xiàn)斐波那契數(shù)。

01#include "stdio.h"
02#include "math.h"
03 
04int factorial(int n);
05 
06int main(void)
07{
08    int i, n, rs;
09 
10    printf("請(qǐng)輸入斐波那契數(shù)n:");
11    scanf("%d",&n);
12 
13    rs = factorial_tail(n, 1, 1);
14    printf("%d ", rs);
15 
16    return 0;
17}
18 
19int factorial_tail(int n,int acc1,int acc2)
20{
21    if (n < 2)
22    {
23        return acc1;
24    }
25    else
26    {
27        return factorial_tail(n-1,acc2,acc1+acc2);
28    }
29}

程序員運(yùn)行結(jié)果如下:

1請(qǐng)輸入斐波那契數(shù)n:20
26765
3Process returned 0 (0x0)   execution time : 1.460 s
4Press any key to continue.

快了一倍有多。當(dāng)然這是不完全統(tǒng)計(jì),有興趣的話可以自行計(jì)算大規(guī)模的值,這里只是介紹尾遞歸而已。

我們可以打印一下程序的執(zhí)行過(guò)程,函數(shù)加入下面的打印語(yǔ)句:

01int factorial_tail(int n,int acc1,int acc2)
02{
03    if (n < 2)
04    {
05        return acc1;
06    }
07    else
08    {
09        printf("factorial_tail(%d, %d, %d) \n",n-1,acc2,acc1+acc2);
10        return factorial_tail(n-1,acc2,acc1+acc2);
11    }
12}

程序運(yùn)行結(jié)果:

01請(qǐng)輸入斐波那契數(shù)n:10
02factorial_tail(9, 1, 2)
03factorial_tail(8, 2, 3)
04factorial_tail(7, 3, 5)
05factorial_tail(6, 5, 8)
06factorial_tail(5, 8, 13)
07factorial_tail(4, 13, 21)
08factorial_tail(3, 21, 34)
09factorial_tail(2, 34, 55)
10factorial_tail(1, 55, 89)
1155
12Process returned 0 (0x0)   execution time : 1.393 s
13Press any key to continue.

從上面的調(diào)試就可以很清晰地看出尾遞歸的計(jì)算過(guò)程了。acc1就是第n個(gè)數(shù),而acc2就是第n與第n+1個(gè)數(shù)的和,這就是我們前面講到的“迭代”的精髓,計(jì)算結(jié)果參與到下一次的計(jì)算,從而減少很多重復(fù)計(jì)算量。

fibonacci(n-1,acc2,acc1+acc2)真是神來(lái)之筆,原本樸素的遞歸產(chǎn)生的棧的層次像二叉樹(shù)一樣,以指數(shù)級(jí)增長(zhǎng),但是現(xiàn)在棧的層次卻像是數(shù)組,變成線性增長(zhǎng)了,實(shí)在是奇妙,總結(jié)起來(lái)也很簡(jiǎn)單,原本棧是先擴(kuò)展開(kāi),然后邊收攏邊計(jì)算結(jié)果,現(xiàn)在卻變成在調(diào)用自身的同時(shí)通過(guò)參數(shù)來(lái)計(jì)算。

小結(jié)

尾遞歸的本質(zhì)是:將單次計(jì)算的結(jié)果緩存起來(lái),傳遞給下次調(diào)用,相當(dāng)于自動(dòng)累積。

在Java等命令式語(yǔ)言中,尾遞歸使用非常少見(jiàn),因?yàn)槲覀兛梢灾苯佑醚h(huán)解決。而在函數(shù)式語(yǔ)言中,尾遞歸卻是一種神器,要實(shí)現(xiàn)循環(huán)就靠它了。

很多人可能會(huì)有疑問(wèn),為什么尾遞歸也是遞歸,卻不會(huì)造成棧溢出呢?因?yàn)榫幾g器通常都會(huì)對(duì)尾遞歸進(jìn)行優(yōu)化。編譯器會(huì)發(fā)現(xiàn)根本沒(méi)有必要存儲(chǔ)棧信息了,因而會(huì)在函數(shù)尾直接清空相關(guān)的棧。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多