zoukankan      html  css  js  c++  java
  • 递归算法详解

    http://blog.csdn.net/effective_coder/article/details/8742979
                                                                                    递归算法详解

            C语言通过运行时堆栈来支持递归的调用,在我们刚接触递归的时候,国内很多教材都采用求阶乘和菲波那契数列来描述该思想,就如同深受大家敬爱的国产的C语言程序设计,老谭也用了阶乘来描述递归,以至于很多新手一看见阶乘就理所当然的认为是递归,坑了不少人,说实在的,描述这个思想还是可以,但是利用递归求阶乘可是没有一点好处,递归解决菲波那契数列效率更是低得惊人,这点是显而易见的!废话不多说,接下来我们进入正题!(不过说实话,我很讨厌接下来这些太理论的东西,说到底就是那么个意思,大家懂就好了,也可以当看看故事!我主要说的就是各种各样递归的实例)

    1:递归算法的思想

     递归算法是把问题转化为规模缩小了的同类问题的子问题。然后递归调用函数(或过程)来表示问题的解。在C语言中的运行堆栈为他的存在提供了很好的支持,过程一般是通过函数或子过程来实现。

    递归算法:在函数或子过程的内部,直接或者间接地调用自己的算法。

    2:递归算法的特点

    递归算法是一种直接或者间接地调用自身算法的过程。在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解。
    递归算法解决问题的特点:
    (1) 递归就是在过程或函数里调用自身。
    (2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。
    (3) 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。所以一般不提倡用递归算法设计程序。
    (4) 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。所以一般不提倡用递归算法设计程序。

    3:递归算法的要求

    递归算法所体现的“重复”一般有三个要求:
    一是每次调用在规模上都有所缩小(通常是减半);
    二是相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入);
    三是在问题的规模极小时必须用直接给出解答而不再进行递归调用,因而每次递归调用都是有条件的(以规模未达到直接解答的大小为条件),无条件递归调用将会成为死循环而不能正常结束。

    4:各式各样利用递归的问题

    1:首先看看那些传统的问题吧,如使用递归来解决斐波那契数列的第n个数是多少?(开始从1开始)

     

    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个数字,那时间可不知道等了多久,调用函数达到了上千次,速度太慢,对于这种情况,我们对比一下不使用的递归的时候时间消耗,这里只需要多加一个函数即可

    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:递归的汉诺塔

    这个程序以及说明在分治算法那一节已经说了,递归和分治通常都是结合在一起使用的,一次次的缩小范围,而且子问题和原问题具有相同的结构!  这里我直接把汉诺塔代码拷贝过来,就不多说了!

    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个板子的移动方案: ",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(" ");    
    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的划分组成。

    现在可以依据这个递推原理写出程序:

    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 整数的全排列问题:

    全排列的递归实现也就是不停的交换两个数的位置,题目描述这里就省了,直接上代码!

    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 ",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 ",pstr);  
    36.     foo(pstr);  
    37.     system("pause");  
    38.     return 0;  
    39. }  


    时间紧促,有时间再继续举例!持续更新

  • 相关阅读:
    HDU 1874 畅通工程续(dijkstra)
    HDU 2112 HDU Today (map函数,dijkstra最短路径)
    HDU 2680 Choose the best route(dijkstra)
    HDU 2066 一个人的旅行(最短路径,dijkstra)
    关于测评机,编译器,我有些话想说
    测评机的优化问题 时间控制
    CF Round410 D. Mike and distribution
    数字三角形2 (取模)
    CF Round410 C. Mike and gcd problem
    CF Round 423 D. High Load 星图(最优最简构建)
  • 原文地址:https://www.cnblogs.com/aabbcc/p/6131211.html
Copyright © 2011-2022 走看看