[蓝桥杯]常用算法-递归法

在前一篇文章中已经介绍了如何应用穷举法来解决具体的编程问题。穷举法最为常用,可以解决大部分常见的问题。今天再来介绍一种新的解题思路: 递归法。 它可以看成是对穷举法的一种补充。它的思路是在不方便穷举所有的可能时,通过设置特定的函数,在该函数内部反复地调用自身并输入不同的参数,以达到遍历所有可能性的目的。使用递归法代码量小,编码简单,逻辑清晰,不易出错。下面通过一个经典的例子来让大家感受下递归法的应用方法。

例1: 汉诺塔问题

汉诺塔问题是最经典的只能够使用递归的方法解决的问题,题目描述如下:

据传说,在古代世界中心的贝拿勒斯(印度北部)的圣庙里,一块在黄铜板上插着3根宝石针。印度教的主神梵天在创造世界时,在其中的一根针上自下而上地穿好了由大至小的64层金片,即为汉诺塔。无论白天黑夜,总有一个僧侣按如下的法则移动这些金片,一次只能够移动一层,不管在哪根针上,小片必须在大片的上面。 要求借助于第二根针将整个汉诺塔移至第三根针上。 图示如下:


假设汉诺塔只有4层,其具体的移动过程如下动画所示:


本题具有如下的特点,使之不能使用穷举法:

1. 不便于穷举: 如果用数学的方法估算,假设一秒钟移动一片,将64层的汉诺塔移至另一根针上所需的步骤是个天文数字:共需接近5845亿年。 由于取值范围过大,且无法循环,不可能将所有的过程都穷举实现出来。

      2. 可以经由一系列有限的步骤实现,其中的每个步骤都很相似,这道题为例,初始时有n层,第一大步就是将上面的n-1层移动至第三根针上,再将最下面的第n层移动至第二根针上,再将第三根针上的n-1层借助第一根针移动至第二根针上。在移动n-1层时,所使用的过程与移动n层是相同的,无非是初始针,目标针和辅助针和移动的层数不同而已。

只要满足这样的条件,都可以试着使用递归的方法来设计,过程如下:

定义函数 moveHanNoi(层数,初始针,辅助针,目标针)

{

if(层数==1) 

{直接从初始针移动至目标针}

else

{

moveHanNoi(层数-1,初始针,目标针,辅助针)   //将(层数-1)层汉诺塔由初始针,利用目标针移至辅助针

直接从初始针移动第n层至目标针

moveHanNoi(层数-1,辅助针,初始针,目标针)   //将(层数-1)层汉诺塔由辅助针,利用初始针移至目标

}

}


这样一来,只需要第一次输入初始人层数,初始针,辅助针,目标针的参数,即可得到结果。省却了复杂的循环遍历的麻烦。

像这种在一个函数内部又调用自身,只是每次调用时传递参数不同的现象,称为递归。


下面再举两个例子来强化大家对于递归算法的认识:

例2: 给定一个正整数,输出它的阶乘。

数学中阶乘的定义就是从1开始逐步累乘自然数到当前的数,使用!来表示阶乘,如

1! = 1

2! = 1*2

3!=1*2*3

4! = 1*2*3*4

……

很快大家就会想到使用穷举法,确实,用穷举法能够非常直接地解决此问题,核心代码如下(C#)

……

int sum =1;

for(int i=1; i<=n; i++)

sum *= i;

输出sum的结果


再来仔细地分析阶乘的分解过程,很容易发现了如下的规律:

1! =1

2! = 2*1! = 2

3! = 3*2!=3*2*1! = 6

4! = 4*3!=4*3*2!=4*3*2*1! = 24


总结如下: 1的阶乘为1,其它任何数的阶乘等于n乘以(n-1)的阶乘。 这里为了求得n的阶乘,必须用同样的方法求得(n-1)的阶乘,再与当前数相乘得到结果。这与汉诺塔中的递归过程何其相似。设计递归的算法如下:

定义函数  jiecheng(int 当前数) 返回当前数的阶乘

{

if(当前数 == 1) 返回1;

else 返回当前数与(当前数-1)阶乘的乘积

}

用C#语言实现如下:

int jiecheng(int curNum){return curNum==1?1:curNum*jiecheng(curNum-1);}


例3: 在8*8的国际象棋棋盘上,放置了8个皇后,使之不能相互攻击,找出所有满足条件的布局

分析: 

(1) 由于国际象棋中皇后的威力最大,横、竖、斜三个方向均可攻击。若想8个皇后和平共处,必须保证棋盘上横、竖、斜三个方向上最多只有一个皇后。

(2) 8*8的棋盘可以使用8*8的二维数组来表示,0代表空,1代表皇后,将此二维数组输出即可表示棋盘的状态。

(3) 以列为单位,自左至右依次地在每一列中由上到下放置皇后,每放置一个之后,看它是否与现有的皇后相冲突。若没有冲突,继续试探下一列。若有冲突,尝试摆放到下一行。最终当试探完最后一列且无冲突之后,输出结果。

设行号为0-7, 列号为0-7 其执行过程如下:

定义函数   place(当前列号,棋盘)   //表示试探棋盘上的某一列

{

if(列号==8)

{输出当前棋盘的状态,返回;}

从第0行遍历至第7行,每次遍历时

{

1. 放置皇后,当前位置为1

2.观察是否与已有的皇后位置冲突 //可设置一个函数来进行判断

无冲突时: 试探下一列,调用自身place(当前列号+1,棋盘)

3. 撤消皇后所在的位置

}

}


通过上面的例子,可以得出递归法的编程模式如下:

1. 尝试着做某种最基本的操作,该操作将会改变现有的状态

汉诺塔: 移动一层塔,导致初始针,辅助针,目标针的状态改变。

阶乘: 当前数乘以比它小一的数的阶乘,改变了累乘的结果。

八皇后: 放置一个皇后在棋盘格式里,当前棋盘的状态改变了。

2. 根据当前的状态,观察是否到了 不需要递归的时刻,如:

汉诺塔: 前n-1层已经移动至辅助针,直接将当前层移动至目标针,不需要递归。

阶乘: 当前数为1时,不需要递归,直接返回1

八皇后:0-7列全部试探完毕,不再递归

3.如果未到不必递归时,递归地调用自身,注意每次调用时的参数不同:

汉诺塔: 若当前n-1层未完全移至辅助针,需要递归调用自身,使用的参数“层数”,“初始针”,“目标针”,“辅助针”都会发生变化

阶乘: 当前数不为1时,递归调用阶乘本身,参数要变成“当前数-1”,返回值为当前数*下一阶乘数

4. 撤消当前的操作,状态还原(可选)

八皇后问题中,在某一列上放置了皇后,无论是否合适,都要在试探完毕之后撤走,再放置到下一位置。这个撤消的操作在迷宫类的题目中非常重要,否则无法得到所有的可能状态。但汉诺塔与阶乘问题中不需要状态还原。


作业:

1. 根据上述的分析,使用C#语言实现6层汉诺塔问题的解法。假设3根针分别为A,B,C,初始时A针上自小而大叠放着6层塔片,要求输出移动到C针上时的全部具体的路径。

2. 求解8皇后问题。要求使用二维整型的数组,其中0值表示空,1值表示皇后,使用0与1的矩阵来表示满足条件的棋盘状态,要求找出所有可能的结果。 

下图即为一种输出状态

00000100

01000000

00000010

10000000

00010000

00000001

00001000

00100000

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值