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. }  


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

  • 相关阅读:
    OpenGL实现通用GPU计算概述
    Android Camera API/Camera2 API 相机预览及滤镜、贴纸等处理
    OpenGL中的Shader
    Android平台Camera实时滤镜实现方法探讨(三)--通过Shader实现YUV转换RBG
    GPU:并行计算利器
    双摄像头测距的OpenCV实现
    Android Camera 通过V4L2与kernel driver的完整交互过程
    图像缩放算法
    双camera景深计算 (1)
    error: ‘shared_ptr’ in namespace ‘std’ does not name a type
  • 原文地址:https://www.cnblogs.com/aabbcc/p/6131211.html
Copyright © 2011-2022 走看看