編者按:尾遞迴最大的好處就是不需要儲存當前函式的現場,在彙編級別即不需要call和set了
1、遞迴
關於遞迴的概念,我們都不陌生。簡單的來說遞迴就是乙個函式直接或間接地呼叫自身,是為直接或間接遞迴。一般來說,遞迴需要有邊界條件、遞迴前進段和遞迴返回段。當邊界條件不滿足時,遞迴前進;當邊界條件滿足時,遞迴返回。用遞迴需要注意以下兩點:
(1) 遞迴就是在過程或函式裡呼叫自身。(2) 在使用遞迴策略時,必須有乙個明確的遞迴結束條件,稱為遞迴出口。
遞迴一般用於解決三類問題:
(1)資料的定義是按遞迴定義的。(fibonacci函式,n的階乘)
(2)問題解法按遞迴實現。(回溯)
(3)資料的結構形式是按遞迴定義的。(二叉樹的遍歷,圖的搜尋)
遞迴的缺點:
遞迴解題相對常用的演算法如普通迴圈等,執行效率較低。因此,應該盡量避免使用遞迴,除非沒有更好的演算法或者某種特定情況,遞迴更為適合的時候。
在遞迴呼叫的過程當中系統為每一層的返回點、區域性量等開闢了棧來儲存,因此遞迴次數過多容易造成棧溢位。
用線性遞迴實現fibonacci函式,程式如下所示:
1遞迴寫的**非常容易懂,完全是根據函式的條件進行選擇計算機步驟。例如現在要計算n=5時的值,遞迴呼叫過程如下圖所示:int fibonaccirecursive(intn)2
2、尾遞迴
顧名思義,尾遞迴就是從最後開始計算, 每遞迴一次就算出相應的結果, 也就是說, 函式呼叫
出現在呼叫者函式的尾部, 因為是尾部, 所以根本沒有必要去儲存任何區域性變數
. 直接讓被呼叫的函式返回時越過呼叫者, 返回到呼叫者的呼叫者去。尾遞迴就是把當前的運算結果(或路徑)放在引數裡傳給下層函式
,深層函式所面對的不是越來越簡單的問題,而是越來越複雜的問題,因為引數裡帶有前面若干步的運算路徑。
尾遞迴是極其重要的,不用尾遞迴,函式的堆疊耗用難以估量,需要儲存很多中間函式的堆疊。比如f(n, sum) = f(n-1) + value(n) + sum; 會儲存n個函式呼叫堆疊,而使用尾遞迴f(n, sum) = f(n-1, sum+value(n)); 這樣則只保留後乙個函式堆疊即可,之前的可優化刪去。
採用尾遞迴實現fibonacci函式,程式如下所示:
1例如現在要計算n=5時的值,尾遞迴呼叫過程如下圖所示:int fibonaccitailrecursive(int n,int ret1,int
ret2)
2
從圖可以看出,為遞迴不需要向上返回了,但是需要引入而外的兩個空間來保持當前的結果。
為了更好的理解尾遞迴的應用,寫個程式進行練習。採用直接遞迴和尾遞迴的方法求解單鏈表的長度,c語言實現程式如下所示:
1 #include 2 #include 3程式測試結果如下圖所示:4 typedef struct
node
5node,*linklist;910
void initlinklist(linklist*head)
1117
18void insertnode(linklist* head,int
d)19
2526
//直接遞迴求鍊錶的長度
27int
getlengthrecursive(linklist head)
2833
//採用尾遞迴求鍊錶的長度,借助變數acc儲存當前鍊錶的長度,不斷的累加
34int getlengthtailrecursive(linklist head,int *acc)
3541
42void
printlinklist(linklist head)
4350 printf("
->null\n");
51}5253
intmain()
54
參考:
遞迴與尾遞迴總結
1 遞迴 關於遞迴的概念,我們都不陌生。簡單的來說遞迴就是乙個函式直接或間接地呼叫自身,是為直接或間接遞迴。一般來說,遞迴需要有邊界條件 遞迴前進段和遞迴返回段。當邊界條件不滿足時,遞迴前進 當邊界條件滿足時,遞迴返回。用遞迴需要注意以下兩點 1 遞迴就是在過程或函式裡呼叫自身。2 在使用遞迴策略時...
演算法 遞迴與尾遞迴總結
前言 今天上網看帖子的時候,看到關於尾遞迴的應用 大腦中感覺這個詞好像在 見過,但是又想不起來具體是怎麼回事。如是乎,在網上搜了一下,頓時豁然開朗,知道尾遞迴是怎麼回事了。下面就遞迴與尾遞迴進行總結,以方便日後在工作中使用。1 遞迴 關於遞迴的概念,我們都不陌生。簡單的來說遞迴就是乙個函式直接或間接...
學習遞迴(總結)
學習遞迴 總結 遞迴是設計和描述演算法的一種有力的工具,由於它在複雜演算法的描述中被經常採用,為此在進一步介紹其他演算法設計方法之前先討論它。能採用遞迴描述的演算法通常有這樣的特徵 為求解規模為n的問題,設法將它分解成規模較小的問題,然後從這些小問題的解方便地構造出大問題的解,並且這些規模較小的問題...