微信扫一扫打赏支持

递归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、本题通过方向的变化来表示步数的增加,也就是线段的变化,其实挺好

*/

posted @ 2017-06-02 11:08  范仁义  阅读(1098)  评论(0编辑  收藏  举报