C语言实现最短迷宫路径问题
试题:迷宫的最短路径
问题描述
给定一个大小为N*M的迷宫。迷宫由通道和墙壁组成,每一步可以向邻接的上下左右四个的通道移动。请求出从起点到终点所需的最小步数。注意,本题假设从起点不一定可以移动到终点。
试题分析
这是一道经典的宽度优先搜索算法(BFS)试题,BFS中,只要将已经访问过的状态用标记管理起来,就可以很好的做到由进及远的搜索。
此外,还需用到队列的思想先进先出,及第一次程序中到达迷宫某一位置的所需步数并记录下来,这样,当其余路径也经过这一地点时(其BFS次数在第一次到达该地点之后),所需的步数必定大于第一次经过该地点所需步数。
将队列这一思想带入到迷宫中就是:假定走迷宫按照下右上左的顺序走动。将起点放入队列,从队列中拿出队列首部数据进行第一次循环,具体及起点向下能走吗,如果能,将起点向下的这一位置放入队列尾部,如果不能,起点能向右走吗,如果能…然后将能走的位置记为1,及从起点到该位置最短路径为1,进行完第一次循环之后,假设队列现在存放的依次是起点下方,起点右方,然后进行第二次循环,从队列中拿出首部数据(及起点下方),依次判断能否向下右上左移动,此时我们需要一个判断条件,来避免反复走到同一位置(例如起点向下走后,又向上走回到了起点,我们不需要这样步数为2的路径),解决方法是,创立一个和迷宫同样大小或者远远大于迷宫大小的二维数组H,然后将数组每个位置初始为一个不可能的路径步数,例如10000或者-1等等,假设初始为10000,那么数组中所有10000的点及代表迷宫中未到达过的点,这样,我们把起点对应数组位置设为0,第一次循环后的位置对应数组设为1,这样我们再次进行循环时,若发现此位置对应数组中已经有了到达此位置的步数,则不去进行操作。
那么想清楚这些之后,所谓队列又需要怎么去建立呢?可以创建一个数组,拿的时候从首部拿,存的时候从尾部存,但这个想法实践起来有很多问题,例如数组究竟设为多大合适,又怎样判断哪个数据是队列的首部和尾部呢?
更优的方法是,创建动态链表,使head指针永远指向链表首部,拿出数据时从head指向的数据拿,然后head指向head的下一个结构体,存放数据时,链表末尾指向NULL的结构体现在指向新建立的结构体(存放新数据的结构体),然后新建立的结构体指向NULL。其实,就是我们编写了两个函数实现了C++的push(将数据保存到队列尾部)和pop(从队列首部取出数据)操作。
这样经过一层一层循环,到最后H数组中终点对应位置的数据即为最短路径所需步数,当然,同学们也可以修改代码输出最短的具体路径是什么。就这样啦~
具体操作方法
代码如下:(“#”在代码中输入迷宫时代表了墙壁,通道随意输入为同一字符即可)
#include<stdio.h>
#include<stdlib.h>
#define INF 10000
#define LEN sizeof(struct hua)
char a[100][100]; //表示迷宫的字符数组
int n, m; //表示迷宫大小
int sx, sy; //起点坐标
int gx, gy; //终点坐标
int wei[100][100]; //到各个位置的最短距离的数组
int ax, ay; //ax,ay为从队列前部取出的对应值
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
struct hua
{
int nx;
int ny;
struct hua *next;
};
int main()
{
int bfs(struct hua * head);
int i;
int res;
struct hua * head;
scanf("%d %d",&n,&m); //输入迷宫大小n*m
scanf("%d %d",&sx,&sy); //输入起点
scanf("%d %d",&gx,&gy); //输入终点
for(i=0;i<n;i++)
scanf("%s",a[i]);
head = (struct hua *) malloc(LEN);
head->nx = sx; //将起点坐标赋给第一个建立的链表首位
head->ny = sy;
head->next = NULL;
res = bfs(head);
if(res == INF)
printf("无法到达");
else
printf("%d",res);
return 0;
}
struct hua * push(int x, int y,struct hua * q) //添加前需要尾指针
{
struct hua * p3;
p3 = (struct hua *) malloc(LEN);
p3->nx = x;
p3->ny = y;
q->next = p3;
p3->next = NULL;
return p3; //返回链表的尾指针
}
struct hua * pop(struct hua * w) //取出需要头指针
{
struct hua * head;
ax = w->nx;
ay = w->ny;
head = w->next;
free(w);
return head; //返回链表的头指针
}
int bfs(struct hua * head)
{
int i, j;
struct hua * c = head, * d = head; //c定位头指针,d定位尾指针
for(i=0;i<n;i++) //把所有的位置都初始化为INF
for(j=0;j<m;j++)
wei[i][j] = INF;
wei[sx][sy] = 0; //主函数已加入起点位置,这里将距离起点设置为0
while(c != NULL) //不断循环直到队列无元素
{
c = pop(c);
if(ax == gx && ay == gy)
break;
for(i=0;i<4;i++) //四个方向循环
{
int ux = ax + dx[i];
int uy = ay + dy[i]; //移动后的位置记ux,uy
if(0<=ux && ux<n && 0<=uy && uy<m && a[ux][uy] != '#' && wei[ux][uy] == INF) //判断是否可以移动以即是否已经访问过
{
if(c == NULL)
{
c = d = (struct hua *) malloc(LEN);
c->nx = ux;
c->ny = uy;
d->next = NULL;
}
else
d = push(ux, uy, d);
wei[ux][uy] = wei[ax][ay] + 1;
}
}
}
return wei[gx][gy];
}
不懂BFS及队列思想的小伙伴建议先搜索了解一下
可参照注释帮助理解,OVER ! ! ! !