算法--递归

递归:

注意两点:
1.边界条件时( 有时有return,有时是别的操作代码里用橘红色标出
2.递归时参数变化(规模变小) 代码里用黄色标出

 递归:你打开面前这扇门,看到屋里面还有一扇门(这门可能跟前面打开的门一样大小(静),也可能门小了些(动)),你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门,你继续打开,。。。, 若干次之后,你打开面前一扇门,发现只有一间屋子,没有门了。 你开始原路返回,每走回一间屋子,你数一次,走到入口的时候,你可以回答出你到底用这钥匙开了几扇门。

    循环:你打开面前这扇门,看到屋里面还有一扇门,(这门可能跟前面打开的门一样大小(静),也可能门小了些(动)),你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门,(前面门如果一样,这门也是一样,第二扇门如果相比第一扇门变小了,这扇门也比第二扇门变小了(动静如一,要么没有变化,要么同样的变化)),你继续打开这扇门,。。。,一直这样走下去。 入口处的人始终等不到你回去告诉他答案。


该用户这么总结到:

    递归就是有去(递去)有回(归来)。


具体来说,为什么可以”有去“? 

    这要求递归的问题需要是可以用同样的解题思路来回答除了规模大小不同其他完全一样的问题。


为什么可以”有回“?

  这要求这些问题不断从大到小,从近及远的过程中,会有一个终点,一个临界点,一个baseline,一个你到了那个点就不用再往更小,更远的地方走下去的点,然后从那个点开始,原路返回到原点


上面的解释几乎回答了我已久的疑问:为什么我老是有递归没有真的在解决问题的感觉?

    因为递是描述问题,归是解决问题。而我的大脑容易被递占据,只往远方去了,连尽头都没走到,何谈回的来。


《漫谈递归:递归的思想》这篇文章将递归思想归纳为:

    递归的基本思想是把规模大的问题转化为规模小的相似的子问题来解决。在函数实现时,因为解决大问题的方法和解决小问题的方法往往是同一个方法,所以就产生了函数调用它自身的情况。另外这个解决问题的函数必须有明显的结束条件,这样就不会产生无限递归的情况了。


    需注意的是,规模大转化为规模小是核心思想,但递归并非是只做这步转化,而是把规模大的问题分解为规模小的子问题和可以在子问题解决的基础上剩余的可以自行解决的部分。而后者就是归的精髓所在,是在实际解决问题的过程。


    我试图把我理解到递归思想用递归用程序表达出来,确定了三个要素:递 + 结束条件 + 归。

function recursion(大规模)
{
    if (end_condition)
    {
        end;     
    }
    else
    {     //先将问题全部描述展开,再由尽头“返回”依次解决每步中剩余部分的问题
        recursion(小规模);     //go;
        solve;                //back;
    }
}

    但是,我很容易发现这样描述遗漏了我经常会遇到的一种递归情况,比如递归遍历的二叉树的先序。我将这种情况用如下递归程序表达出来。

function recursion(大规模)
{
    if (end_condition)
    {
        end;     
    }
    else
    {     //在将问题转换为子问题描述的每一步,都解决该步中剩余部分的问题。
        solve;                //back;
        recursion(小规模);     //go;
    }
}

 其实理解递归可能没有“归”,只有去(分治)的情况后,我们应该想到递归也许可以既不需要在“去”的路上解决问题,也不需要在“归”的路上解决问题,只需在路的尽头解决问题,即在满足停止条件时解决问题。递归的分治思想不一定是要把问题规模递归到最小,还可以是将问题递归穷举其所有的情形,这时通常递归的表达力体现在将无法书写的嵌套循环(不确定数量的嵌套循环)通过递归表达出来。

将这种递归情形用递归程序描述如下:

recursion()
{
    if (end_condition)
    {
        solve;     
    }
    else
    {     //在将问题转换为子问题描述的每一步,都解决该步中剩余部分的问题。
        for () { recursion();     //go; }
    }
}
 递归的基本思想是广义地把规模大的问题转化为规模小的相似的子问题或者相似的子问题集合来解决。 广义针对规模的,规模的缩小具体可以是指递归函数的参数,也可以是其参数之一。相似是指解决大问题的方法和解决小问题的方法往往是同一个方法,还可以是指解决子问题集的各子问题的方法是同一个方法。解决大问题的方法可以是由解决次规模问题的方法和解决剩余部分的方法组成,也可以是由一系列解决次规模问题的方法组成。


二、常见的递归问题

1.汉诺塔问题

如图,汉诺塔问题是指有三根杆子A,B,C。C杆上有若干碟子,把所有碟子从A杆上移到C杆上,每次只能移动一个碟子,大的碟子不能叠在小的碟子上面。求最少要移动多少次?

当n=1时:
Move  1  from  A  to  C
当n=2时:
Move  1  from  A  to  B
Move  2  from  A  to  C
Move  1  from  B  to  C

当n=3时:
Move  1  from  A  to  C
Move  2  from  A  to  B
Move  1  from  C  to  B
Move  3  from  A  to  C
Move  1  from  B  to  A
Move  2  from  B  to  C
Move  1  from  A  to  C

源代码

[java]  view plain  copy
  1. static StringBuffer str = new StringBuffer();  
  2.     /** 
  3.      * //汉诺塔问题 
  4.      * @param n 盘子的个数 
  5.      * @param x 将要移动盘子柱子 
  6.      * @param y 要借用的柱子 
  7.      * @param z 要移动到的柱子 
  8.      * @return 
  9.      */  
  10.     public static String hanio(int n, Object x, Object y, Object z) { //参数:n个盘子,借助y从x移到z上
  11.         //String str ="";  
  12.         if(1 == n)   
  13.             str.append(move(x, n, z) + "\n");  //结束的临界情况
  14.         else {  
  15.             hanio(n-1, x, z, y);  //缩小规模(参数减小)借助z把x上的移到y上
  16.             str.append(move(x, n, z) + "\n") ;  //步骤写到str上
  17.             hanio(n-1, y, x, z);  //缩小规模(参数减小)借助x把y上的移到z上
  18.         }  
  19.         return str.toString();  //结束后返回str(移动步骤)
  20.     }  
  21.     private static String move(Object x, int n, Object y) {  //写移动步骤的函数
  22.         //System.out.println("Move  " + n + "  from  " + x + "  to  " + y);  
  23.         return "Move  " + n + "  from  " + x + "  to  " + y;  
  24.     }  

2、n的阶乘



[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. //计算n!   
  3. double factorial(double n)  
  4. {  
  5.     //边界条件  
  6.     if(n==1)  
  7.         return 1;  
  8.     //递归方程  
  9.     else  
  10.         return n*factorial(n-1);  
  11. }  
  12.   
  13. int main()  
  14. {  
  15.     int n;  
  16.     printf("please input n\n");  
  17.     scanf("%d",&n);  
  18.     printf("%d! =%.2f\n",n,factorial(n));  
  19.     return 0;  
  20. }  


3、Fibonacci数列

无穷数列1,1,2,3,5,8,13,21,34,55, … ,被称为Fibonacci数列。它可以递归地定义为: 



[cpp]  view plain  copy
  1. #include <stdio.h>  
  2.   
  3. //计算斐波那契数列的第n项   
  4. double Fibonacci(int n){  
  5.     //边界条件  
  6.     if(n<=2)  
  7.         return 1;  
  8.     //递归方程  
  9.     else  
  10.         return Fibonacci(n-1)+Fibonacci(n-2);  //递归
  11. }  
  12.   
  13. int main(){  
  14.     int n;  
  15.     printf("please input n \n");   
  16.     scanf("%d",&n);  
  17.     printf("Fibbnicc(%d) = %.2f\n",n,Fibonacci(n));  
  18.     return 0;  
  19. }  

4、集合元素的全排列


[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #define Length 5  
  3.   
  4. //输出R中所有元素  
  5. void printAllElement(int* R){  
  6.     int i;  
  7.     for(i = 0; i < Length; i++){  
  8.         printf("%d ",R[i]);  
  9.     }  
  10.     printf("\n");  
  11. }  
  12. //交换R[i],R[j]  
  13. void swap(int R[],int i, int j){  
  14.     int temp;  
  15.     temp = R[i];  
  16.     R[i] = R[j];  
  17.     R[j] = temp;  
  18. }  
  19.   
  20. //递归排列 R[k]--R[m]中的(m-k+1)个元素   
  21. void perm(int* R,int start,int finish){  
  22.     //边界条件(R中只有一个元素)  
  23.     if(start == finish){  
  24.         printAllElement(R);  
  25.     }  
  26.     //递归方程  
  27.     else{  
  28.         //循环指定R中的每个元素ri  
  29.         for(int i = start; i < finish; i++){  
  30.             //通过交换R[i]/R[start]实现指定R[i]到首位置   
  31.             swap(R,start,i);  
  32.             //对R中剩下的元素R(start+1,finish)进行排列  
  33.             perm(R,start+1,finish);  
  34.             //换回原位置  
  35.             swap(R,start,i);  
  36.         }  
  37.     }  
  38. }  
  39.   
  40. int main(){  
  41.     int R[]={1,2,3,4,5};  
  42.     perm(R,0,5);  
  43.     return 0;  
  44. }  

5、正整数的划分

 将正整数n表示成一系列正整数之和:n=n1 +n2+ … +nk ,其中n1≥n2≥ … ≥nk≥1,k≥1,这种表示称为正整数n的划分。求正整数n的不同划分个数。 




[cpp]  view plain  copy
  1. #include <stdio.h>  
  2.   
  3. //递归求解正整数n的划分数p(n)=q(n,n)   
  4. int qIntegerDivision(int n,int m){  
  5.       
  6.     //边界条件 q(1,1)=1, (n=1,m=1)   
  7.     if(n == 1 && m == 1){  
  8.         return 1;  
  9.     }  
  10.       
  11.     //递归方程 q(n,n), (n<m)   
  12.     if(n < m){  
  13.         return qIntegerDivision(n,n);  
  14.     }  
  15.       
  16.     //递归方程 1+q(n,n-1), (n=m)   
  17.     if(n == m){  
  18.         return 1 + qIntegerDivision(n,n-1);  
  19.     }  
  20.       
  21.     //递归方程 q(n,m-1) + q(n-m,m), (n>m>1)   
  22.     if(n > m && m > 1){  
  23.         return qIntegerDivision(n,m-1) + qIntegerDivision(n-m,m);  
  24.     }  
  25. }  
  26.   
  27. int main(){  
  28.     int n,m;  
  29.     printf("please input n,m\n");  
  30.     scanf("%d,%d",&n,&m);  
  31.     printf("q(%d,%d)=%d\n",n,m,qIntegerDivision(n,m));  
  32.     return 0;  
  33. }  

6、Ackerman函数

当一个函数及其它的变量是由函数自身定义时,称这个函数是双递归函数。


[cpp]  view plain  copy
  1. #include <stdio.h>  
  2.   
  3. //Ackerman双递归函数   
  4. int ackerman(int n,int m){  
  5.       
  6.     //边界条件 A(1,0)=2   
  7.     if(n == 0 && m == 1){  
  8.         return 0;  
  9.     }  
  10.       
  11.     //边界条件 A(0,m)=1, m>=0   
  12.     if(n == 0 && m >= 0){  
  13.         return 1;  
  14.     }  
  15.       
  16.     //边界条件 A(n,0)=n+2, n>=2   
  17.     if(n >= 2 && m == 0){  
  18.         return n + 2;  
  19.     }  
  20.       
  21.     //递归方程 A(n,m)=A(A(n-1,m),m-1), n,m>=1   
  22.     if(n >= 1 && m >= 1){  
  23.         return ackerman(ackerman(n-1,m),m-1);  
  24.     }  
  25. }  
  26.   
  27. int main(){  
  28.     int n,m;  
  29.     printf("please input n,m\n");  
  30.     scanf("%d,%d",&n,&m);  
  31.     printf("A(%d,%d)=%d\n",n,m,ackerman(n,m));  
  32.     return 0;  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值