这是一道深搜加广搜的题目,在房间里模拟走迷宫走了两圈才搞清楚原理,因为在一些小细节的地方出错,花了很久才解决了这道题,对我来说还是很有意义的一道题,用的是C写的
题目大意:
在迷宫中从起点走到终点,每走一步加一,起点和终点也要加一。要求输出三种不同的移动方式所需的步数,分别是一直靠着左边走、一直靠着右边走、最短路径。靠左和靠右走有点不好理解,接下来重点研究一下。
难点:
- 靠左走的意思是:每走一步都可以向上下左右四个方向旋转,但是旋转的方向具有顺序,靠左的方向顺序为左上右下。当站在一个点的时候,可以想象一下你面对的是哪一个方向,然后就判断这个方向的左边能不能转过去,如果能就左转,不能就自身旋转90度面对下一个方向,再判断当前面对方向的左边能否转过去,一直重复该过程直到可以左转为止。注意,当左转过去以后,你面对的方向就改变成了左转之前的方向的左边的那个方向(可能有点晕,之后会有配图解释)。只需要一个深搜不断去左转移动直到找到终点为止
- 靠右同理,只是把左转变为右转。靠右的方向顺序为右上左下
- 最短路径只需要广搜就可以
以靠左为例:
- 左、上、右、下分别由0、1、2、3代表,nextL[4][2] = {{0,-1}, {-1,0}, {0,1}, {1,0}}按照左上右下的顺序控制移动,假设当前面对的方向为face左侧方向为nextFace,那么face永远是nextFace的后一个方向(face=1 则nextFace=0)
- 当无法左转时,face就要加一从而旋转到下一个位置, nextFace也要跟着改变,只需要(face+3)%4就可以使其变为face的前一个位置(因为nextL数组下标最多到3所以要对4取模将nextFace的大小控制在0~3)
- 当可以左转时,face的值就变成它的左侧的值,即face=nextFace
以靠左为例作图进行解释,靠右类似
箭头指向的方向是面对的方向,若左侧可以走,就左转,同时转过去后,面对的方向也变成之前的左侧
若左侧不可以走,就按照旋转顺序旋转90度,如图,此时面对的方向由向上变成了向右,左侧也由左变成了上,若仍然无法左转就以此类推继续旋转直到可以左转
此时新的左侧可以转过去,就向左转,同时转过去后,面对的方向也变成之前的左侧
完整代码如下:
#include<stdio.h>
#include<string.h>
struct note {
int x ;
int y ;
int step ;
}que[1605];
int n, m, ansL, ansR ;
char map[45][45] ;
int nextL[4][2] = {{0,-1}, {-1,0}, {0,1}, {1,0}} ; //靠左旋转顺序 左上右下
int nextR[4][2] = {{0,1}, {-1,0}, {0,-1}, {1,0}} ; //靠右旋转顺序 右上左下
int book[45][45] ;
int startX, startY ;
void dfs(int x, int y, int face, int way, int step) { //深搜解决靠左或者靠右
if(map[x][y] == 'E') {
if(way == 1) //way为1是靠左
ansL = step ;
else //其他是靠右
ansR = step ;
return ;
}
int nextFace = (face + 3) % 4; //当前面向的方向的左侧或者右侧
int tx, ty ;
for(int i=0; i<4; i++) {
if(way == 1) { //左
tx = x + nextL[nextFace][0] ;
ty = y + nextL[nextFace][1] ;
}
else { //右
tx = x + nextR[nextFace][0] ;
ty = y + nextR[nextFace][1] ;
}
if(tx<0 || tx>=m || ty<0 || ty>=n || map[tx][ty]=='#') {
face++ ; //当前方向无法移动就旋转
nextFace = (face + 3) % 4 ; //旋转后的左侧或者右侧也需要改变
continue ;
}
if(map[tx][ty] != '#') {
face = nextFace ; //左转(右转)后面对的方向改变
dfs(tx, ty, face, way, step+1) ;
break ; //一个点移动后就不再查看其他方向
}
}
return ;
}
int bfs() { //最短路径
memset(book, 0, sizeof(book)) ;
int head, tail, tx, ty;
head = tail = 0 ;
que[tail].x = startX ;
que[tail].y = startY ;
que[tail++].step = 1 ;
book[startX][startY] = 1 ;
while(head < tail) {
for(int i=0; i<4; i++) {
tx = que[head].x + nextL[i][0] ;
ty = que[head].y + nextL[i][1] ;
if( tx<0 || tx>=m || ty<0 || ty>=n || map[tx][ty]=='#' )
continue ;
if(map[tx][ty] == 'E') //到达终点就退出
return que[head].step+1 ;
if(map[tx][ty] == '.' && book[tx][ty]==0) {
que[tail].x = tx ;
que[tail].y = ty ;
que[tail++].step = que[head].step + 1 ;
book[tx][ty] = 1 ;
}
}
head++ ;
}
}
int main() {
int t ;
int flag ;
scanf("%d", &t) ;
while(t--) {
scanf("%d%d", &n, &m) ;
for(int i=0; i<m; i++)
scanf("%s", map[i]) ;
flag = 0 ;
for(int i=0; i<m; i++) {
for(int j=0; j<n; j++) {
if(flag)
break ;
if(map[i][j] == 'S') {
startX = i ;
startY = j ;
flag = 1 ;
}
}
}
dfs(startX, startY, 0, 1, 1) ; //左 初始面对的方向没有影响
dfs(startX, startY, 0, 2, 1) ; //右 初始面对的方向没有影响
printf("%d %d %d\n", ansL, ansR, bfs()) ; //最短路径
}
return 0 ;
}
总结:
- 这道题就是左和右的时候有点不好理解,只要想通了就简单了。如果实在理解不了可以像我一样在房间里模拟着这样的走法走一走可能就会懂了。
- 在提交这道题的时候一直出现runtime error的问题,在找了好久之后实在没办法问了大佬才发现广搜求最短路径中没有加一个book数组来标记走过的点,最开始写代码的时候都已经写了,在后来检查的时候鬼使神差的就把它删了结果就会出现死循环出现runtime error,而且样例也没有出错就一直没有发现
- 解决了runtime error后成了wrong answer,自己画了个地图检查发现还是广搜出问题了,最后要返回结果时返回的是末尾的数据,正确的应该是head中的数据。使用样例得到的结果是正确的,其实是碰巧末尾的数据和结果一样才会迷惑人
- 样例的偶然性很大,应该自己多想想一些特殊测试数据多尝试