遞歸算法詳解


http://blog.csdn.net/effective_coder/article/details/8742979
                                                                                遞歸算法詳解

        C語言通過運行時堆棧來支持遞歸的調用,在我們剛接觸遞歸的時候,國內很多教材都采用求階乘和菲波那契數列來描述該思想,就如同深受大家敬愛的國產的C語言程序設計,老譚也用了階乘來描述遞歸,以至於很多新手一看見階乘就理所當然的認為是遞歸,坑了不少人,說實在的,描述這個思想還是可以,但是利用遞歸求階乘可是沒有一點好處,遞歸解決菲波那契數列效率更是低得驚人,這點是顯而易見的!廢話不多說,接下來我們進入正題!(不過說實話,我很討厭接下來這些太理論的東西,說到底就是那么個意思,大家懂就好了,也可以當看看故事!我主要說的就是各種各樣遞歸的實例)

 

1:遞歸算法的思想

 遞歸算法是把問題轉化為規模縮小了的同類問題的子問題。然后遞歸調用函數(或過程)來表示問題的解。在C語言中的運行堆棧為他的存在提供了很好的支持,過程一般是通過函數或子過程來實現。

遞歸算法:在函數或子過程的內部,直接或者間接地調用自己的算法。

 

2:遞歸算法的特點

遞歸算法是一種直接或者間接地調用自身算法的過程。在計算機編寫程序中,遞歸算法對解決一大類問題是十分有效的,它往往使算法的描述簡潔而且易於理解。
遞歸算法解決問題的特點:
(1) 遞歸就是在過程或函數里調用自身。
(2) 在使用遞歸策略時,必須有一個明確的遞歸結束條件,稱為遞歸出口。
(3) 遞歸算法解題通常顯得很簡潔,但遞歸算法解題的運行效率較低。所以一般不提倡用遞歸算法設計程序。
(4) 在 遞歸調用的過程當中系統為每一層的返回點、局部量等開辟了棧來存儲。遞歸次數過多容易造成 棧溢出等。所以一般不提倡用遞歸算法設計程序。

3:遞歸算法的要求

遞歸算法所體現的“重復”一般有三個要求:
一是每次調用在規模上都有所縮小(通常是減半);
二是相鄰兩次重復之間有緊密的聯系,前一次要為后一次做准備(通常前一次的輸出就作為后一次的輸入);
三是在問題的規模極小時必須用直接給出解答而不再進行 遞歸調用,因而每次遞歸調用都是有條件的(以規模未達到直接解答的大小為條件),無條件遞歸調用將會成為死循環而不能正常結束。

4:各式各樣利用遞歸的問題

1:首先看看那些傳統的問題吧,如使用遞歸來解決斐波那契數列的第n個數是多少?(開始從1開始)

 

[cpp]  view plain copy
 print?
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. int Fib(int index);  
  5. int main(int argc, char* argv[])  
  6. {  
  7.     cout<<Fib(12)<<endl;  
  8.     system("pause");  
  9.     return 0;  
  10. }  
  11.   
  12. int Fib(int index)  
  13. {  
  14.     if(index==1 || index==2)  
  15.         return index;  
  16.     else  
  17.         return Fib(index-1) + Fib(index-2);    //開始遞歸調用  
  18.   
  19. }  


寫程序的時候我測試了一下,假如要第100個數字,那時間可不知道等了多久,調用函數達到了上千次,速度太慢,對於這種情況,我們對比一下不使用的遞歸的時候時間消耗,這里只需要多加一個函數即可

[cpp]  view plain copy
 print?
  1. #include <iostream>  
  2. #include <ctime>  
  3. using namespace std;  
  4.   
  5. int Fib2(int index);  
  6. int Fib1(int index);  
  7. int main(int argc, char* argv[])  
  8. {  
  9.     clock_t start,finish;  
  10.   
  11.     cout<<"不使用遞歸:"<<endl;  
  12.     start = clock();  
  13.     cout<<"所得結果為 "<<Fib2(40)<<endl;  
  14.     finish = clock();  
  15.     cout<<"時間消耗為 "<<finish - start<<"毫秒"<<endl;  
  16.   
  17.     cout<<endl;  
  18.     cout<<"使用遞歸:"<<endl;  
  19.     start = clock();  
  20.     cout<<"所得結果為 "<<Fib1(40)<<endl;  
  21.     finish = clock();  
  22.     cout<<"時間消耗為 "<<finish - start<<"毫秒"<<endl;  
  23.   
  24.     system("pause");  
  25.     return 0;  
  26. }  
  27.   
  28. int Fib1(int index)  
  29. {  
  30.     if(index==1 || index==2)  
  31.         return index;  
  32.     else  
  33.         return Fib1(index-1) + Fib1(index-2);    //開始遞歸調用  
  34. }  
  35.   
  36. int Fib2(int index)  
  37. {  
  38.     if(index == 1 || index ==2)  
  39.         return index;  
  40.     int *array = new int [index+1];  
  41.     array[1]=1;                //第0個元素沒有使用  
  42.     array[2]=2;  
  43.     for(int i=3;i<=index;++i)  
  44.         array[i] = array[i-1] + array[i-2];  
  45.     return array[index];  
  46. }  


運行結果:

結果顯而易見,差距太明顯,在這里我們同時求第40個斐波那契數字比較時間消耗,所以大家可以看到遞歸的時間消耗是非常嚴重,而且效率非常低下,上面已經說了,在可以不用遞歸的時候盡量不用,那么遞歸是不是一無是處勒?答案是否定的,在很多程序設計大賽中,有很多題用一般的思路是很難解的,或者是過程繁瑣,如果適當的利用遞歸,結果將事半功倍!!!

 

 2:遞歸的漢諾塔

這個程序以及說明在分治算法那一節已經說了,遞歸和分治通常都是結合在一起使用的,一次次的縮小范圍,而且子問題和原問題具有相同的結構!  這里我直接把漢諾塔代碼拷貝過來,就不多說了!

 

[cpp]  view plain copy
 print?
  1. #include <stdio.h>    
  2. #include <stdlib.h>    
  3.     
  4. static int count = -1;    
  5.     
  6. void move(char x,char y);                             // 對move函數的聲明     
  7. void hanoi(int n,char one,char two,char three)       ;// 對hanoi函數的聲明\    
  8.     
  9. int main()    
  10. {              
  11.     int m;    
  12.     printf("請輸入一共有多少個板子需要移動:");    
  13.     scanf("%d",&m);    
  14.     printf("以下是%d個板子的移動方案:\n",m);    
  15.     hanoi(m,'A','B','C');    
  16.     system("pause");    
  17.     return 0;    
  18. }    
  19.     
  20. void hanoi(int n,char one,char two,char three)        // 定義hanoi函數      
  21. // 將n個盤從one座借助two座,移到three座     
  22. {    
  23.         
  24.     if(n==1)    
  25.         move(one,three);    
  26.     else    
  27.     {    
  28.         hanoi(n-1,one,three,two);                   //首先把n-1個從one移動到two    
  29.         move(one,three);                            //然后把最后一個n從one移動到three    
  30.         hanoi(n-1,two,one,three);                   //最后再把n-1個從two移動到three    
  31.     }    
  32. }    
  33.     
  34.     
  35. void move(char x,char y)                           //  定義move函數     
  36. {    
  37.     count++;    
  38.     if( !(count%5) )    
  39.         printf("\n");    
  40.     printf("%c移動至%c  ",x,y);    
  41. }    


3:兔子繁殖問題(遞歸實現)

 

一對小兔子一年后長成大兔子,一對大兔子每半年生一對小兔子,大兔子的繁殖期為4年,兔子的壽命為6年,假定第一年年初投放了一對小兔子,請編程實現,第N年年末總共有多少只兔子,N由鍵盤輸入!

解析,這個題目比較好懂,也就是一對小兔子前一年長大,然后每半年產一對小兔子,持續4年,然后最后一年不生殖了,再過一年死亡,題目看似簡單,其實要想遞歸起來可不是那么容易的,大家可以想一下!

代碼如下:
 

 

4:整數的划分問題

將一個整數分解為若干個整數之和的形式,比如 n = n1+n2+n3+n4··········!不同划分的個數稱為N的划分數。

例如對於6而言:

6;

5+1;

4+2,4+1+1;

3+3;3+2+1;3+1+1+1;

2+2+2;2+2+1+1;2+1+1+1+1;

1+1+1+1+1+1     一共有6種!

 

1、 q(n,1) = 1 ,n>=1 ;
當最大加數不大於1時,任何正整數n只有一種表示方式:n = 1+1+……+1 。n個1的和。
2、q( n,m ) = q( n,n ),n<=m;  最大加數不能大於n。
3、 q( n,n ) = 1 +  q( n , n-1 );   正整數的划分由n1=n和n1<=n的划分組成。
4、q( n,m ) = q( n,m-1 )+q( n-m,m ), n>m>1;正整數n的最大加數不大於m的划分由 n1=m的划分和n1<m的划分組成。

現在可以依據這個遞推原理寫出程序:

 

[cpp]  view plain copy
 print?
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. int intPart( int n , int m ) ;  
  4. int main()  
  5. {  
  6.     int num ;  
  7.     int partNum = 0 ;  
  8.     printf("Please input an integer:/n") ;  
  9.     scanf("%d",&num) ;  
  10.     partNum = intPart(num,num);  
  11.     printf("%d/n",partNum) ;  
  12.     system("pause");  
  13.     return 0;  
  14. }  
  15. int intPart( int n , int m )  
  16. {  
  17.     if( ( n < 1 ) ||( m < 1 ) ) return 0 ;  
  18.     if( ( n == 1 )||( m == 1 ) ) return 1 ;  
  19.     if( n < m ) return intPart( n , n ) ;  
  20.     if( n == m ) return intPart( n , m-1 ) + 1 ;  
  21.     return intPart( n , m-1 ) + intPart( n - m , m ) ;  
  22. }  

運行結果可以看到一共有11種情況

 

5 整數的全排列問題:

全排列的遞歸實現也就是不停的交換兩個數的位置,題目描述這里就省了,直接上代碼!

 

[cpp]  view plain copy
 print?
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. void swap(char *a,char *b)  
  5. {  
  6.     char temp = *a;  
  7.     *a = *b;  
  8.     *b = temp;  
  9. }  
  10. //k表示循環到第幾個字符,m表示該次循環的總長度  
  11. void arrange(char *pizstr,int k,int m)  
  12. {  
  13.     if(k == m)  
  14.     {  
  15.         static int m_count = 1;  
  16.         printf("the %d time:%s\n",m_count++,pizstr);  
  17.     }  
  18.     else  
  19.     {  
  20.         for(int i=k;i<=m;i++)                          //主要遞歸球全排列的代碼  
  21.         {  
  22.             swap(pizstr+k,pizstr+i);  
  23.             arrange(pizstr,k+1,m);  
  24.             swap(pizstr+k,pizstr+i);  
  25.         }  
  26.     }  
  27. }  
  28. void foo(char *p_str)  
  29. {  
  30.     arrange(p_str,0,strlen(p_str)-1);  
  31. }  
  32. int main()  
  33. {  
  34.     char pstr[] = "12345";  
  35.     printf("%s\n",pstr);  
  36.     foo(pstr);  
  37.     system("pause");  
  38.     return 0;  
  39. }  


時間緊促,有時間再繼續舉例!持續更新


免責聲明!

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



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