前言:今天上網看帖子的時候,看到關於尾遞歸的應用(http://bbs.csdn.net/topics/390215312),大腦中感覺這個詞好像在哪里見過,但是又想不起來具體是怎么回事。如是乎,在網上搜了一下,頓時豁然開朗,知道尾遞歸是怎么回事了。下面就遞歸與尾遞歸進行總結,以方便日后在工作中使用。
1、遞歸
關於遞歸的概念,我們都不陌生。簡單的來說遞歸就是一個函數直接或間接地調用自身,是為直接或間接遞歸。一般來說,遞歸需要有邊界條件、遞歸前進段和遞歸返回段。當邊界條件不滿足時,遞歸前進;當邊界條件滿足時,遞歸返回。用遞歸需要注意以下兩點:(1) 遞歸就是在過程或函數里調用自身。(2) 在使用遞歸策略時,必須有一個明確的遞歸結束條件,稱為遞歸出口。
1 int FibonacciRecursive(int n) 2 { 3 if( n < 2) 4 return n; 5 return (FibonacciRecursive(n-1)+FibonacciRecursive(n-2)); 6 }
遞歸寫的代碼非常容易懂,完全是根據函數的條件進行選擇計算機步驟。例如現在要計算n=5時的值,遞歸調用過程如下圖所示:
2、尾遞歸
顧名思義,尾遞歸就是從最后開始計算, 每遞歸一次就算出相應的結果, 也就是說, 函數調用出現在調用者函數的尾部, 因為是尾部, 所以根本沒有必要去保存任何局部變量. 直接讓被調用的函數返回時越過調用者, 返回到調用者的調用者去。尾遞歸就是把當前的運算結果(或路徑)放在參數里傳給下層函數,深層函數所面對的不是越來越簡單的問題,而是越來越復雜的問題,因為參數里帶有前面若干步的運算路徑。
尾遞歸是極其重要的,不用尾遞歸,函數的堆棧耗用難以估量,需要保存很多中間函數的堆棧。比如f(n, sum) = f(n-1) + value(n) + sum; 會保存n個函數調用堆棧,而使用尾遞歸f(n, sum) = f(n-1, sum+value(n)); 這樣則只保留后一個函數堆棧即可,之前的可優化刪去。
采用尾遞歸實現Fibonacci函數,程序如下所示:
1 int FibonacciTailRecursive(int n,int ret1,int ret2) 2 { 3 if(n==0) 4 return ret1; 5 return FibonacciTailRecursive(n-1,ret2,ret1+ret2); 6 }
例如現在要計算n=5時的值,尾遞歸調用過程如下圖所示:
從圖可以看出,為遞歸不需要向上返回了,但是需要引入而外的兩個空間來保持當前的結果。
為了更好的理解尾遞歸的應用,寫個程序進行練習。采用直接遞歸和尾遞歸的方法求解單鏈表的長度,C語言實現程序如下所示:
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 typedef struct node 5 { 6 int data; 7 struct node* next; 8 }node,*linklist; 9 10 void InitLinklist(linklist* head) 11 { 12 if(*head != NULL) 13 free(*head); 14 *head = (node*)malloc(sizeof(node)); 15 (*head)->next = NULL; 16 } 17 18 void InsertNode(linklist* head,int d) 19 { 20 node* newNode = (node*)malloc(sizeof(node)); 21 newNode->data = d; 22 newNode->next = (*head)->next; 23 (*head)->next = newNode; 24 } 25 26 //直接遞歸求鏈表的長度 27 int GetLengthRecursive(linklist head) 28 { 29 if(head->next == NULL) 30 return 0; 31 return (GetLengthRecursive(head->next) + 1); 32 } 33 //采用尾遞歸求鏈表的長度,借助變量acc保存當前鏈表的長度,不斷的累加 34 int GetLengthTailRecursive(linklist head,int *acc) 35 { 36 if(head->next == NULL) 37 return *acc; 38 *acc = *acc+1; 39 return GetLengthTailRecursive(head->next,acc); 40 } 41 42 void PrintLinklist(linklist head) 43 { 44 node* pnode = head->next; 45 while(pnode) 46 { 47 printf("%d->",pnode->data); 48 pnode = pnode->next; 49 } 50 printf("->NULL\n"); 51 } 52 53 int main() 54 { 55 linklist head = NULL; 56 int len = 0; 57 InitLinklist(&head); 58 InsertNode(&head,10); 59 InsertNode(&head,21); 60 InsertNode(&head,14); 61 InsertNode(&head,19); 62 InsertNode(&head,132); 63 InsertNode(&head,192); 64 PrintLinklist(head); 65 printf("The length of linklist is: %d\n",GetLengthRecursive(head)); 66 GetLengthTailRecursive(head,&len); 67 printf("The length of linklist is: %d\n",len); 68 system("pause"); 69 }
程序測試結果如下圖所示:
參考:http://www.cnblogs.com/JeffreyZhao/archive/2009/03/26/tail-recursion-and-continuation.html