日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

漫谈递归:从斐波那契开始了解尾递归

發(fā)布時間:2025/6/15 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 漫谈递归:从斐波那契开始了解尾递归 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

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

尾遞歸wiki解釋如下:

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

我們還是從簡單的斐波那契開始了解尾遞歸吧。

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

01 #include "stdio.h"
02 #include "math.h"
03 ?
04 int?factorial(int?n);
05 ?
06 int?main(void)
07 {
08 ????int?i, n, rs;
09 ?
10 ????printf("請輸入斐波那契數(shù)n:");
11 ????scanf("%d",&n);
12 ?
13 ????rs = factorial(n);
14 ????printf("%d \n", rs);
15 ?
16 ????return?0;
17 }
18 ?
19 // 遞歸
20 int?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 }

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

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

在i5的CPU下也要花費 3.502 秒的時間。

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

01 #include "stdio.h"
02 #include "math.h"
03 ?
04 int?factorial(int?n);
05 ?
06 int?main(void)
07 {
08 ????int?i, n, rs;
09 ?
10 ????printf("請輸入斐波那契數(shù)n:");
11 ????scanf("%d",&n);
12 ?
13 ????rs = factorial_tail(n, 1, 1);
14 ????printf("%d ", rs);
15 ?
16 ????return?0;
17 }
18 ?
19 int?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 }

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

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

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

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

01 int?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 }

程序運行結(jié)果:

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

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

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

小結(jié)

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

在Java等命令式語言中,尾遞歸使用非常少見,因為我們可以直接用循環(huán)解決。而在函數(shù)式語言中,尾遞歸卻是一種神器,要實現(xiàn)循環(huán)就靠它了。

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

《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀

總結(jié)

以上是生活随笔為你收集整理的漫谈递归:从斐波那契开始了解尾递归的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。