一、递归的定义
定义: 当函数直接或者间接调用自己时,则发生了递归。
二、理解递归
先举个递归在阶乘中的例子:阶乘的定义: “一个正整数的阶乘是所有小于或等于该数的正整数的积,并且0的阶乘为1。”
以下是C++的实现:
int factorial(int n) { if(n <= 1) return 0; else return n * factorial(n-1); }
分析:我们很容易判断当n = 某个较小数值时这个阶乘的递归计算是否是正确。比如当n = 4时, 那么factoria(4)
等于4 * factoria(3)
, 而factoria(3)
等于3 * factoria(2)
,factoria(2)
等于2 * factoria(1),
等于2 * 1
,所以factoria(4)
等于4 * 3 * 2 * 1。
反思:这种分析已经有点门路了,可是怎么拓展到一般情况呢?
Paul Graham提到一种方法, 给我很大启发, 该方法如下:
- 当n=0,1的时候,结果正确;
- 假设函数对于n是正确的, 函数对n+1结果也正确。
- 如果这两点是成立的,我们知道这个函数对于所有可能的n都是正确的。
这种方法很像数学归纳法, 也是递归正确的思考方式, 事实上, 阶乘的递归表达方式就是1! = 1,n! = (n-1)! × n
,当程序实现符合算法描述的时候, 程序自然对了, 假如还不对, 那是算法本身错了…… 相对来说,n,n+1的情况为通用情况, 虽然比较复杂, 但是还能理解, 最重要的, 也是最容易被新手忽略的问题在于第1点, 也就是基本用例(base case)要对. 比如, 上例中, 我们去掉if(n<=1)
的判断后, 代码会进入死循环, 永远不会结束。
三、寻找问题的递归算法
在解题时,想运用递归却无从下手?
你只需要做两件事情:
- 示范如何解决问题的一般情况, 通过将问题切分成有限小并更小的子问题;
- 示范如何通过有限的步骤,来解决最小的问题(基本用例);
- 因为递归每次都将问题变得更小,而一个有限的问题终究会被解决的,而最小的问题仅需几个有限的步骤就能解决。
四、实战运用
汉诺塔问题:有三根杆子A,B,C。A杆上有N个(N>1)穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至C杆:1.每次只能移动一个圆盘;2.大盘不能叠在小盘上面。
如何使用递归?
一般情况:
当有N个圆盘在A上,我们已经找到办法将其移到C杆上了,我们怎么移动N+1个圆盘到C杆上呢? 很简单,
我们首先用将N个圆盘移动到C上的方法将N个圆盘都移动到B上, 然后再把第N+1个圆盘(最后一个)移动到C上,
再用同样的方法将在B杆上的N个圆盘移动到C上。问题解决。
基本用例:
当有1个圆盘在A上,我们直接把圆盘移动到C上即可。
C++代码:
1 // 汉诺塔 2 # include <stdio.h> 3 void hanoi ( int n, char a, char b, char c ) //这里代表将a柱子上的盘子借助b柱子移动到c柱子 4 { if (1 == n) //如果是一个盘子直接将a柱子上的盘子移动到c 5 { 6 printf("%c-->%c ",a,c); 7 } 8 else 9 { 10 hanoi ( n-1, a, c, b ) ; //将a柱子上n-1个盘子借助c柱子,移动到b柱子 11 printf("%c-->%c ",a , c) ; //再直接将a柱子上的最后一个盘子移动到c 12 hanoi ( n-1, b, a, c ) ; //然后将b柱子上的n-1个盘子借助a移动到c 13 } 14 } 15 int main () 16 { int n ; 17 printf( "Input the number of diskes:") ; 18 scanf("%d",&n) ; 19 hanoi ( n, 'A' , 'B' , 'C' ) ; 20 return 0; 21 }