本文仅供个人记录和复习,不用于其他用途
递归的概念
所谓的递归,最开始是由汉诺塔问题提出的。简单来讲,我们如果要解决一个大的问题,不妨先想一想小一点的问题如何解决。假如这个小问题也解决不了,那么就想一想更小的。将这个过程一直持续下去,那么总会出现一个最小的问题,能够被我们轻松解决。那么反过来,最大的问题也能够被层层推出,得到最终的答案。汉诺塔问题我就不再赘述了,这里只讲一讲其他有代表性的问题。
例题
用递归将数据转换成二进制
我们如何将一个十进制数转换成二进制数?步骤我们都知道,就是用短除法来转换。看一看短除法的过程,是不是有点像递归呢?
|
|
我们使用短除法就是将数值除以2,保留每次的余数,一直到数值为0时,将余数倒序输出,就是该数值的二进制表达了。这里的change()
递归调用之所以在printf()
前面,是因为我们需要倒序输出,所以必须从最后一个余数开始输出。当然,这个程序不仅仅用于转换二进制,我们只需要修改一下,就能变成转换三进制或其他进制等等。
跳阶梯
这里有一个比较有意思的问题:现在有一个阶梯,你可以一次跨1阶或者2阶。如果这个阶梯有50阶,那么走完这个阶梯有多少种方法?不要被这个长长的阶梯给吓住了,试着将问题缩小:
- 当阶梯数为1时,我们就只能跨1阶,共有1种方法
- 当阶梯数为2时,我们既可以跨两次1阶,也可以跨2阶,共有2种方法
很显然,这两种情况就是阶梯问题的基本答案,我们递归时就要从这两个方面来寻找答案。如果我们想要走上第3阶,我们可以通过两种方式:
- 从第1阶跨2阶
- 从第2阶跨1阶
而当我们处在第1或第2阶时,答案已经知道了,所以共有1+2=3
种方法。至于往上走,同样也是这种解决方法。通式:digui(n - 1) + digui(n - 2)
。
|
|
对于3阶以上的,无非就是跨1阶还是跨2阶,所以将这两种情况相加即可。这里还给出来另外一种解法,那就是使用循环,不过这个循环其实就相当于递归中的三种情况。n3
代表的是当前阶数所用的方法数,n2
代表的是前1阶所用的方法数,n1
代表的是前2阶所用的方法数,本质上就是不断往上移动。
这里要注意的是,数据类型使用的是double
。其实对于50阶来说,总方法数很多,所以要使用double
。另外,大家也不用真的去计算走到50阶有多少种方法,因为这个数值太大,一般的计算机无法在短时间内计算出来。
跳阶梯升级版
假如我们加大一下难度,我们一次可以跨1、2、…、n阶,那么跨上第n阶有多少种方法?这里不用急,我们一个个的看:
- digui(1) = 1
- digui(2) = digui(1)
- digui(3) = digui(1) + digui(2)
- digui(n - 1) = digui(1) + … + digui(n - 2)
- digui(n) = digui(1) + … + digui(n - 2) + digui(n - 1)
请注意,digui(n - 1)
其实就是digui(1) + … + digui(n - 2)
,而这两者相加则等于digui(n)
,也就是说digui(n)
其实就是2 * digui(n - 1)
。
|
|
矩形覆盖
再来看一个问题:现在有无数个2×1的小矩形,以及一个2×n的大矩形(n为非负整数),请问要让小矩形不重叠的覆盖大矩形,共有多少种方法?问题看似复杂,其实也是斐波拉契数列的应用。小矩形有横和竖两种摆法,分情况讨论如下:
- n = 0,0种方法
- n = 1,1种方法
- n = 2,2种方法
- n = 3,f(2) + f(1)
- n = n,f(n - 1) + f(n - 2)
当n为3时,我们可以竖着摆一个小矩形,然后剩下的就是f(2)
。或者是横着摆两个小矩形,剩下的就是f(1)
。当问题被扩展时,就成为了斐波拉契数列。
八皇后问题
这个问题比较经典,说的是在8×8的棋盘上,如果摆放上八个皇后,使得八个皇后之间没有冲突,问有多少种摆法。在国际象棋中,皇后能够向上下左右以及对角线等八个方向移动,也就是说八个皇后所能够移动的方向,都不能够有皇后出现。
其实这个问题并不复杂,既然是在8行8列的棋盘上放8个皇后,那么肯定每行每列都只能有一个皇后。也就是说,我们可以从第1行第1列开始摆放皇后,尝试它所有的可能,然后再尝试第2列等等。至于第2个皇后,自然就是在第1个皇后的基础上递归产生的。
|
|
递归中的逻辑很简单,简单来讲就是从第一行第一列开始,往下探索所有的可能性,对每一个位置安排进行检验,不符合就尝试下一种可能。当达到最后一行,同时检验合格时,一个解就诞生了。当第一列的所有可能性探索完后,开始探索第一行第二列。总而言之,对于每一行,其实都是和第一行一样的,只不过这种循环是层层嵌套的:
|
|
事实上,所有的循环,都能够用递归来表示。但是呢,并不是所有的递归都能用循环来表示。不过,对于大部分情况,我们在理解递归时,可以适当的用循环的思想来辅助理解。递归相对于循环,大大地简化了代码量,不过逻辑没有循环那么清楚。