遞歸與尾遞歸總結


1、遞歸

  關於遞歸的概念,我們都不陌生。簡單的來說遞歸就是一個函數直接或間接地調用自身,是為直接或間接遞歸。一般來說,遞歸需要有邊界條件、遞歸前進段和遞歸返回段。當邊界條件不滿足時,遞歸前進;當邊界條件滿足時,遞歸返回。用遞歸需要注意以下兩點:(1) 遞歸就是在過程或函數里調用自身。(2) 在使用遞歸策略時,必須有一個明確的遞歸結束條件,稱為遞歸出口。

遞歸一般用於解決三類問題:
   (1)數據的定義是按遞歸定義的。(Fibonacci函數,n的階乘)
   (2)問題解法按遞歸實現。(回溯)
   (3)數據的結構形式是按遞歸定義的。(二叉樹的遍歷,圖的搜索)
遞歸的缺點:
  遞歸解題相對常用的算法如普通循環等,運行效率較低。因此,應該盡量避免使用遞歸,除非沒有更好的算法或者某種特定情況,遞歸更為適合的時候。 在遞歸調用的過程當中系統為每一層的返回點、局部量等開辟了棧來存儲,因此遞歸次數過多容易造成棧溢出。
  用線性遞歸實現Fibonacci函數,程序如下所示:
復制代碼
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 }
復制代碼

程序測試結果如下圖所示:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM