学习递归可能是初学者接触编程后较难理解的地方,递归函数在本身“没有写完”时就自己调用了自己。可以尝试通过代码模拟加深对于递归状态设计和递归出口的认识,本文依旧从课上题目和课下练习入手帮助大家进行此部分的复习。
题目一:流感感染
有一批易感人群住在网格状的宿舍区内,宿舍区为n*n的矩阵,每个格点为一个房间,房间里可能住人,也可能空着。在第一天,有些房间里的人得了流感,以后每天,得流感的人会使其邻居传染上流感,(已经得病的不变),空房间不会传染。请输出第m天得流感的人数。
关于输入:第一行一个数字n,n不超过100,表示有n*n的宿舍房间。
接下来的n行,每行n个字符,’.’表示第一天该房间住着健康的人,’#’表示该房间空着,’@’表示第一天该房间住着得流感的人。
接下来的一行是一个整数m,m不超过100.
代码实现:以天数作为阶段划分方式(对于简单的题目,时间和空间是常用的阶段划分依据,其实就是依据题目的要求设计状态)。
#include <iostream>
#include <cstring>
using namespace std;
char map[105][105];
bool visit[105][105];
int n, k;
int gan = 0; // 记录感染的人数,每天新增的感染人数总和即为答案
void liugan(int day) {
memset(visit, true, sizeof(visit));
if (day == k) return; // 递归出口,计算到第k天即可
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (map[i][j] == '@' && visit[i][j] == true) {
visit[i][j] = false;
if (i >= 1) {
if (map[i - 1][j] == '.') {
gan++;
map[i - 1][j] = '@';
}
} if (i <= n - 2) {
if (map[i + 1][j] == '.') {
gan++;
map[i + 1][j] = '@';
visit[i + 1][j] = false;
}
} if (j >= 1) {
if (map[i][j - 1] == '.') {
gan++;
map[i][j - 1] = '@';
}
} if (j <= n - 2) {
if (map[i][j + 1] == '.') {
gan++;
map[i][j + 1] = '@';
visit[i][j + 1] = false;
}
}
}
}
}
day++;
liugan(day); // 递归调用下一天
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> map[i][j];
if (map[i][j] == '@') gan++; // 最初就感染的人数
}
}
cin >> k;
liugan(1);
cout << gan << endl;
return 0;
}
题目二:棋盘问题
在一个给定大小的棋盘上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
关于输入:输入含有多组测试数据。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
关于输出:对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。
【代码实现】做过八皇后的同学都知道这是一个变体,我们状态的划分是第几列,此外还要加上放了几个棋子。
#include <iostream>
using namespace std;
int n, k;
char map[10][10];
int cnt;
bool col[10]; // 记录这一行是否已经有棋子了
void chess(int x, int num) { // x表示当前的列,放第num个棋子
if (x == n) {
if (num == k + 1) cnt++;
return;
}
if (num == k + 1) {
cnt++;
return;
}
// 当前列不放棋子
chess(x + 1, num);
// 当前列放棋子
for (int i = 0; i < n; i++) { // 枚举行数
if (col[i] || map[i][x] == '.') continue;
col[i] = true;
chess(x + 1, num + 1);
col[i] = false; // 回溯
}
}
int main() {
while (1) {
cnt = 0;
cin >> n >> k;
if (n == -1 && k == -1) break;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
cin >> map[i][j];
chess(0, 1);
cout << cnt << endl;
}
return 0;
}
题目三:集合里的乘法
给定一个目标数T和一个整数集合S,判断是否存在S的一个非空子集,子集中的数相乘的积为T。
代码实现:本题和上一题极为相似,可以对应来看。
#include <iostream>
using namespace std;
int t, n;
int s[20];
bool flag;
void f(int x, int sum) { // 枚举第x个元素,sum为当前的乘积
if (x == n + 1) {
if (sum == t) flag = 1;
return;
}
if (sum == t) {
flag = 1;
return;
}
// 不选当前元素
f(x + 1, sum);
// 选择当前元素
f(x + 1, sum * s[x]);
}
int main() {
cin >> t >> n;
for (int i = 1; i <= n; i++) cin >> s[i];
f(1, 1);
if (flag) cout << "YES" << endl;
else cout << "NO" << endl;
return 0;
}
题目四:左手定则
玩家在玩迷宫游戏,经过多次Trial and Error决定用左手定则对迷宫进行探索。左手定则:在每一个路口,我们都选择最左边的方向,左转的优先级最高,其次为向前,最后为右转,如果实在走进了一个死胡同,那就连续右转两次,回头向后走。稍微研究一下这种走迷宫的方法,我们就发现在这个过程中,事实上我们的左手可以始终放在墙上。
但是呢,左手定则并不能保证遍历到迷宫中的每一个点。悲剧的是,某项重要的通关道具被放在了这个迷宫中……幸运的是,游戏迷宫的地图可以绘制出来,现在请求你的帮助。对于一个给定的地图,他想知道是不是能够通过左手定则取得这件道具。
关于输入:多组数据。
对于每组数据,第一行有两个整数N,M代表接下来有n行字符串,每行m个字符,其中0 < n <= 100, 0 < m <= 100,表示一个n × m的迷宫。
其中‘#’表示墙,‘S’表示起点,‘T’表示道具,‘.’表示空地。
接下来是一个方向(NSWE),表示起始面向的方向。
数据保证最外一圈都是墙。
关于输出:对于每组数据输出一行。‘YES’表示可以到达,‘NO’表示无法到达。
代码实现:因为每次转弯玩家的朝向都会改变,因此增加了opt这个状态表示朝向。如果之前到过当前的点而且和之前来的方向相同,就标记为已经走过。
#include <iostream>
#include <cstring>
using namespace std;
int n, m, sx, sy, ex, ey;
char map[110][110], dir;
bool flag;
bool visited[110][110][5];
bool inmap(int x, int y) {
return (x >= 1) && (x <= n) && (y >= 1) && (y <= m) && map[x][y] != '#';
}
void dfs(int x, int y, int opt) { // opt记录上一次的运动方向
visited[x][y][opt] = 1;
if (x == ex && y == ey) {
flag = 1;
return;
}
if (flag) return;
if (x != sx && y != sy && map[x][y] == 'S') return;
if (opt == 1) { // 1表示上一次是向北
if (inmap(x, y - 1)) {
if (visited[x][y - 1][2]) return;
dfs(x, y - 1, 2); // 2表示向西
}
else if (inmap(x - 1, y)) {
if (visited[x - 1][y][1]) return;
dfs(x - 1, y, 1); // 1继续向北
}
else if (inmap(x, y + 1)) {
if (visited[x][y + 1][3]) return;
dfs(x, y + 1, 3); // 3表示向东
}
else { // 走到死胡同
if (visited[x + 1][y][4]) return;
dfs(x + 1, y, 4); // 4表示向南
}
}
else if (opt == 2) { // 向西
if (inmap(x + 1, y)) {
if (visited[x + 1][y][4]) return;
dfs(x + 1, y, 4); // 向南
}
else if (inmap(x, y - 1)) {
if (visited[x][y - 1][2]) return;
dfs(x, y - 1, 2); // 向西
}
else if (inmap(x - 1, y)) {
if (visited[x - 1][y][1]) return;
dfs(x - 1, y, 1);
}
else {
if (visited[x][y + 1][3]) return;
dfs(x, y + 1, 3);
}
}
else if (opt == 3) { // 向东
if (inmap(x - 1, y)) {
if (visited[x - 1][y][1]) return;
dfs(x - 1, y, 1); // 向北
}
else if (inmap(x, y + 1)) {
if (visited[x][y + 1][3]) return;
dfs(x, y + 1, 3); // 向东
}
else if (inmap(x + 1, y)) {
if (visited[x + 1][y][4]) return;
dfs(x + 1, y, 4); // 向南
}
else {
if (visited[x][y - 1][2]) return;
dfs(x, y - 1, 2); // 向西
}
}
else if (opt == 4) { // 向南
if (inmap(x, y + 1)) {
if (visited[x][y + 1][3]) return;
dfs(x, y + 1, 3); // 向东
}
else if (inmap(x + 1, y)) {
if (visited[x + 1][y][4]) return;
dfs(x + 1, y, 4); // 向南
}
else if (inmap(x, y - 1)) {
if (visited[x][y - 1][2]) return;
dfs(x, y - 1, 2); // 向西
}
else {
if (visited[x - 1][y][1]) return;
dfs(x - 1, y, 1); // 向北
}
}
}
int main() {
while (cin >> n >> m) {
memset(visited, 0, sizeof(visited));
flag = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> map[i][j];
if (map[i][j] == 'S') sx = i, sy = j;
if (map[i][j] == 'T') ex = i, ey = j;
}
}
cin >> dir;
if (dir == 'N') dfs(sx, sy, 1);
else if (dir == 'E') dfs(sx, sy, 3);
else if (dir == 'S') dfs(sx, sy, 4);
else dfs(sx, sy, 2);
if (flag) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}