递归1--小游戏
递归1--小游戏
零:本题总结
1、回溯迷宫问题
2、扩充边界
一、递归基本思想
定义:函数直接或者间接调用自身
应用场景:原问题复杂,但是可以划分成许多性质相同的子问题,子问题容易求解
递归写法:1、先写出问题的递推公式
2、递归部分的边界条件就是递推公式中的边界条件
3、递归部分的主体部分就是递推公式中的主体部分
递归在内存中的实现方式:系统通过栈来实现(通过栈去讲)
递归简单实例:http://www.cnblogs.com/Renyi-Fan/p/6914840.html
二、递归实例题目
题目:
有一个w * h 个正方格子的矩形板,每个正方格子上可以有一张游戏卡片, 当然也可以没有,当下面的情况满足时,认为
两个游戏卡片之间有一条路径相连:1、路径只包含水平或者竖直的直线段;2、路径不能穿过别的游戏卡片;3、但是允许
路径临时的离开矩形板。判断是否存在一条满足题意的路径能连接给定的两个游戏卡片。
输入:
输入包括多组数据: 一个矩形板对应一组数据
第一行包括两个整数 w和h (1 <= w, h <= 75),分别表示矩形板的宽度和长度
下面的h行, 每行包括w个字符, 表示矩形板上的游戏卡片分布情况:
使用 ‘X’ 表示这个地方有一个游戏卡片,使用空格 表示这个地方没有游戏卡片
之后每行上包括4个整数:
x1, y1, x2, y2 (1 <= x1, x2 <= w, 1 <= y1, y2 <= h)给出两个卡片在矩形板上的位置
注意: 矩形板左上角的坐标是(1,1),输入保证这两个游戏卡片所处的位置是不相同的
如果一行上有4个0, 表示这组测试数据的结束
如果一行上给出w = h = 0, 那么表示所有的输入结束了
输入样例:
5 4
XXXXX
X X
XXX X
XXX
2 3 5 3
1 3 4 4
2 3 3 4
0 0 0 0
0 0
样例图片及图片说明:
在 (1,3)和 (4,4)处的游戏卡片是可以相连的,
而在 (2,3) 和 (3,4) 处的游戏卡是不相连的, 因为连接它们的每条路径都必须要穿过别的游戏卡片。
本题的问题是:判断是否存在一条满足题意的路径能连接给定的两个游戏卡片。
输出:
对每一个矩形板, 输出一行 “Board #n:”, n是输入数据的编号
对每一组需要测试的游戏卡片输出一行. 这一行的开头是 “Pair m: ”,
这里m是测试卡片的编号(对每个矩形板, 编号都从1开始),
如果可以相连, 找到连接这两个卡片的所有路径中包括线段数最少的路径, 输出 “k segments.”
k是找到的最优路径中包括的线段的数目,
如果不能相连, 输出 “impossible.”,每组数据之后输出一个空行。
输出样例:
Board #1:
Pair 1: 4 segments.
Pair 2: 3 segments.
Pair 3: impossible.
三、分析
题目分析:
普通迷宫问题的路径数目是经过的格子数目
而该问题路径只包含水平或者竖直的直线段,
所以需要记录每一步走的方向
如果上一步走的方向和这一步走的方向相同, 递归搜索时路径数不变, 否则路径数加1
路径只包含水平或者竖直的直线段. 路径不能穿过别的游戏卡片. 但是允许路径临时的离开矩形板
所以在矩形板最外层增加一圈格子, 路径可以通过这些新增加的格子
分析及变量描述:
1. 设置迷宫为二维数组board[][], 数组的值是:
空格: 代表这个地方没有游戏卡片
‘X’ : 代表这个地方有游戏卡片
2. 在搜索过程中, 用另外一个二维数mark[][]标记格子是否已经走过了
mark[i][j]=0 //格子(i, j)未走过
mark[i][j]=1 //格子(i, j)已经走过
3. int minstep, w, h;
//全局变量
//minstep, 记录从起点到达终点最少路径数,
//初始化为一个很大的数
//w, h矩形板的宽度和高度
4. 设置搜索方向顺序是东, 南, 西, 北
int to[4][2] = {{0,1},{1,0},{0,-1},{-1,0}};
//now_x, now_y, 当前位置
//x, y下一步位置
for(i = 0; i < 4; i ++)
{
int x = now_x + to[i][0];
int y = now_y + to[i][1];
f=i; //方向, 0,1,2,3分别表示东,南,西,北 …
}
5. 判断新位置(x, y)是否有效
T1: (x, y)在边界之内 (x > -1) && (x < w + 2) && (y > -1) && (y < h + 2)
T2: 该位置没有游戏卡片并且未曾走过 ((board[y][x] == ' ') && (mark[y][x] == false))
T3: 已经到达终点 (x == end_x) && (y == end_y) && (board[y][x] == 'X‘)
综上, (x,y)有效的条件是 T1 && (T2 || T3)
((x > -1) && (x < w + 2) && (y > -1) && (y < h + 2) && (((board[y][x] == ' ') && (mark[y][x] == false)) || ((x == end_x) && (y == end_y) && (board[y][x] == 'X'))))
四、代码
1 /* 2 递归基本思想: 3 定义:函数直接或者间接调用自身 4 应用场景:原问题复杂,但是可以划分成许多性质相同的子问题,子问题容易求解 5 递归写法:1、先写出问题的递推公式 6 2、递归部分的边界条件就是递推公式中的边界条件 7 3、递归部分的主体部分就是递推公式中的主体部分 8 递归在内存中的实现方式:系统通过栈来实现(通过栈去讲) 9 递归简单实例:http://www.cnblogs.com/Renyi-Fan/p/6914840.html 10 */ 11 12 /* 13 这个太浪费时间了,下次不这么做了 14 本末倒置了 15 题目: 16 有一个w * h 个正方格子的矩形板,每个正方格子上可以有一张游戏卡片, 当然也可以没有,当下面的情况满足时,认为 17 两个游戏卡片之间有一条路径相连:1、路径只包含水平或者竖直的直线段;2、路径不能穿过别的游戏卡片;3、但是允许 18 路径临时的离开矩形板。判断是否存在一条满足题意的路径能连接给定的两个游戏卡片。 19 20 输入: 21 输入包括多组数据: 一个矩形板对应一组数据 22 第一行包括两个整数 w和h (1 <= w, h <= 75),分别表示矩形板的宽度和长度 23 下面的h行, 每行包括w个字符, 表示矩形板上的游戏卡片分布情况: 24 使用 ‘X’ 表示这个地方有一个游戏卡片,使用空格 表示这个地方没有游戏卡片 25 之后每行上包括4个整数: 26 x1, y1, x2, y2 (1 <= x1, x2 <= w, 1 <= y1, y2 <= h)给出两个卡片在矩形板上的位置 27 注意: 矩形板左上角的坐标是(1,1),输入保证这两个游戏卡片所处的位置是不相同的 28 如果一行上有4个0, 表示这组测试数据的结束 29 如果一行上给出w = h = 0, 那么表示所有的输入结束了 30 31 输入样例: 32 5 4 33 XXXXX 34 X X 35 XXX X 36 XXX 37 2 3 5 3 38 1 3 4 4 39 2 3 3 4 40 0 0 0 0 41 0 0 42 43 44 输出: 45 对每一个矩形板, 输出一行 “Board #n:”, n是输入数据的编号 46 对每一组需要测试的游戏卡片输出一行. 这一行的开头是 “Pair m: ”, 47 这里m是测试卡片的编号(对每个矩形板, 编号都从1开始), 48 如果可以相连, 找到连接这两个卡片的所有路径中包括线段数最少的路径, 输出 “k segments.” 49 k是找到的最优路径中包括的线段的数目, 50 如果不能相连, 输出 “impossible.”,每组数据之后输出一个空行。 51 52 输出样例: 53 Board #1: 54 Pair 1: 4 segments. 55 56 Pair 2: 3 segments. 57 58 Pair 3: impossible. 59 */ 60 61 /* 62 题目分析: 63 普通迷宫问题的路径数目是经过的格子数目 64 而该问题路径只包含水平或者竖直的直线段, 65 所以需要记录每一步走的方向 66 如果上一步走的方向和这一步走的方向相同, 递归搜索时路径数不变, 否则路径数加1 67 68 路径只包含水平或者竖直的直线段. 路径不能穿过别的游戏卡片. 但是允许路径临时的离开矩形板 69 所以在矩形板最外层增加一圈格子, 路径可以通过这些新增加的格子 70 71 分析及变量描述: 72 1. 设置迷宫为二维数组board[][], 数组的值是: 73 空格: 代表这个地方没有游戏卡片 74 ‘X’ : 代表这个地方有游戏卡片 75 2. 在搜索过程中, 用另外一个二维数mark[][]标记格子是否已经走过了 76 mark[i][j]=0 //格子(i, j)未走过 77 mark[i][j]=1 //格子(i, j)已经走过 78 3. int minstep, w, h; 79 //全局变量 80 //minstep, 记录从起点到达终点最少路径数, 81 //初始化为一个很大的数 82 //w, h矩形板的宽度和高度 83 4. 设置搜索方向顺序是东, 南, 西, 北 84 int to[4][2] = {{0,1},{1,0},{0,-1},{-1,0}}; 85 //now_x, now_y, 当前位置 86 //x, y下一步位置 87 for(i = 0; i < 4; i ++) 88 { 89 int x = now_x + to[i][0]; 90 int y = now_y + to[i][1]; 91 f=i; //方向, 0,1,2,3分别表示东,南,西,北 … 92 } 93 5. 判断新位置(x, y)是否有效 94 T1: (x, y)在边界之内 (x > -1) && (x < w + 2) && (y > -1) && (y < h + 2) 95 T2: 该位置没有游戏卡片并且未曾走过 ((board[y][x] == ' ') && (mark[y][x] == false)) 96 T3: 已经到达终点 (x == end_x) && (y == end_y) && (board[y][x] == 'X‘) 97 综上, (x,y)有效的条件是 T1 && (T2 || T3) 98 ((x > -1) && (x < w + 2) && (y > -1) && (y < h + 2) && (((board[y][x] == ' ') && (mark[y][x] == false)) || ((x == end_x) && (y == end_y) && (board[y][x] == 'X')))) 99 100 */ 101 102 /* 103 本题伪代码: 104 读入数据 105 扩充边界 106 找最小步数(核心)(递归) 107 输出结果 108 ps:如果是我,我就分四个函数+来写这个题目 109 110 其中递归最小步数的伪代码为: 111 看是不是递归边界,是的话,就返回 112 不是递归边界,就东南西北找下一步(所以这里肯定是循环) 113 如果找到的下一步合理,我们就递归找下一步的下一步 114 注意回溯,不能漏掉同层的情况 115 116 117 自己心得:(一定要精析,不能浮在表面) 118 1、这种多方向的图,我直接画出方向特别容易理解 119 2、这种长方形不要理解为x和y,理解为宽width和高height,而且直接把表格和坐标画出来,直观 120 3、这里是用getchar()读入字符,并且先读上一行的换行符 121 4、用scanf("%d %d %d %d", &begin_x, &begin_y, &end_x, &end_y)&& begin_x > 0来判断输入为0000的情况,因为正常数据x不可能为0 122 5、回溯的模板记好就好了:原数组,标记数组,循环,回溯 123 6、用if(w == 0 && h == 0)break;来判断输入为0 0的情况 124 7、递归的边界条件是到达终点, 递归的递推公式就是上下左右走 ,只不过边界条件和递推公式都做了剪枝 125 8、边界条件的剪枝是当前步数大于能成功的最小步数,所以当前这种情况应该勇敢舍去 126 9、递推公式的剪枝是下一点在边界内,下一点可以走并且没有被走过,或者下一点是终点(这个条件好像可有可无) 127 10、递归函数记录了关键的信息,当前点,下一点,步数和方向,因为我们要求的是步数,而方向决定步数 128 11、如果是我,我会把对当前四边的扩展区域写在一起,当然,扩展边界这个思路特别好 129 12、直接和一个二维数组相加表示东南西北,当然你之前要把这个图画出来,这个挺好的 130 13、本题通过方向的变化来表示步数的增加,也就是线段的变化,其实挺好 131 132 */ 133 134 #include <iostream> 135 #include <cstdio> 136 #include <memory.h> 137 using namespace std; 138 139 #define MAXIN 75 140 char board[MAXIN + 2][MAXIN + 2]; //定义矩形板//储存每个格子中是否有卡片//+2是扩充格子边界 141 142 int minstep, w, h, to[4][2] = {{0,1},{1,0},{0,-1},{-1,0}}; //定义方向//顺序是东, 南, 西, 北 143 bool mark[MAXIN + 2][MAXIN + 2]; //定义标记数组//记录格子有没有被走过,回溯算法中标准的数组 144 145 146 void Search(int now_x, int now_y, int end_x, int end_y, int step, int f){ 147 //now_x, now_y当前位置 148 //end_x, end_y结束位置 149 //step已经走过的路径数目 150 //f从上一步走到(now_x, now_y)时的方向 151 if(step > minstep) return; //当前路径数大于minstep, 返回优化策略,其实也就是剪枝 152 if(now_x == end_x && now_y == end_y){ //到达终点 153 if(minstep > step) //更新最小路径数 154 minstep = step; 155 return; 156 } 157 //下面这部分程序就是一个标准的回溯 158 for(int i = 0; i < 4; i ++){ //枚举下一步的方向 159 int x = now_x + to[i][0]; //得到新的位置 160 int y = now_y + to[i][1]; 161 bool t1= (x > -1) && (x < w + 2) && (y > -1) && (y < h + 2);//(x, y)在边界之内 162 bool t2= (board[y][x] == ' ') && (mark[y][x] == false);//该位置没有游戏卡片并且未曾走过 163 bool t3=(x==end_x)&& (y == end_y) && (board[y][x] == 'X');//已经到达终点 164 if (t1&& (t2 || t3)){//能够继续的条件 t1&& (t2 || t3) 165 mark[y][x] = true; //如果新位置有效标记该位置 166 //已经过上一步方向和当前方向相同, 167 //则递归搜索时step不变, 否则step+1 168 if(f == i) Search(x, y, end_x, end_y, step, i); 169 else Search(x, y, end_x, end_y, step + 1, i); 170 mark[y][x] = false; //回溯, 该位置未曾走过 171 } 172 } 173 } 174 175 int main(){ 176 freopen("in.txt","r",stdin); 177 int Boardnum = 0; 178 while(scanf("%d %d", &w, &h)){ //读入数据 179 if(w == 0 && h == 0)break; 180 Boardnum ++; 181 182 printf("Board #%d:\n", Boardnum); 183 int i, j; 184 //初始化左上角相连的两条边 185 for (i = 0; i < MAXIN + 2; i ++) board[0][i] = board[i][0] = ' '; 186 for(i = 1; i <= h; i ++){ //读入矩形板的布局 187 getchar(); 188 for(j = 1; j <= w; j ++) board[i][j] = getchar(); 189 } 190 //在矩形板最外层增加一圈格子 191 //初始化左下角相连的两条边 192 for (i = 0; i <= w; i ++) 193 board[h + 1][i + 1] = ' '; 194 for (i = 0; i <= h; i ++) 195 board[i + 1][w + 1] = ' '; 196 197 int begin_x, begin_y, end_x, end_y, count = 0; 198 while(scanf("%d %d %d %d", &begin_x, &begin_y, &end_x, &end_y)&& begin_x > 0){ //读入起点和终点 199 200 count ++; 201 minstep = 100000; //初始化minstep为一个很大的值 202 memset(mark, false, sizeof(mark)); 203 //递归搜索 204 Search(begin_x, begin_y, end_x, end_y, 0, -1); 205 //输出结果 206 if(minstep < 100000) 207 printf("Pair %d: %d segments.\n", count, minstep); 208 else printf("Pair %d: impossible.\n", count); 209 printf("\n"); 210 } 211 212 } 213 return 0; 214 }
五、自己心得
/*
本题伪代码:
读入数据
扩充边界
找最小步数(核心)(递归)
输出结果
ps:如果是我,我就分四个函数+来写这个题目
其中递归最小步数的伪代码为:
看是不是递归边界,是的话,就返回
不是递归边界,就东南西北找下一步(所以这里肯定是循环)
如果找到的下一步合理,我们就递归找下一步的下一步
注意回溯,不能漏掉同层的情况
自己心得:(一定要精析,不能浮在表面)
1、这种多方向的图,我直接画出方向特别容易理解
2、这种长方形不要理解为x和y,理解为宽width和高height,而且直接把表格和坐标画出来,直观
3、这里是用getchar()读入字符,并且先读上一行的换行符
4、用scanf("%d %d %d %d", &begin_x, &begin_y, &end_x, &end_y)&& begin_x > 0来判断输入为0000的情况,因为正常数据x不可能为0
5、回溯的模板记好就好了:原数组,标记数组,循环,回溯
6、用if(w == 0 && h == 0)break;来判断输入为0 0的情况
7、递归的边界条件是到达终点, 递归的递推公式就是上下左右走 ,只不过边界条件和递推公式都做了剪枝
8、边界条件的剪枝是当前步数大于能成功的最小步数,所以当前这种情况应该勇敢舍去
9、递推公式的剪枝是下一点在边界内,下一点可以走并且没有被走过,或者下一点是终点(这个条件好像可有可无)
10、递归函数记录了关键的信息,当前点,下一点,步数和方向,因为我们要求的是步数,而方向决定步数
11、如果是我,我会把对当前四边的扩展区域写在一起,当然,扩展边界这个思路特别好
12、直接和一个二维数组相加表示东南西北,当然你之前要把这个图画出来,这个挺好的
13、本题通过方向的变化来表示步数的增加,也就是线段的变化,其实挺好
*/