1. 问题分析
题目:现有一迷宫如下图所示,蓝色部分为墙壁,白色部分为通路,入口在左上角(1,1)处,出口在右下角(8,8)处,试找出一条路径以通过该迷宫(路径不能重叠)。
分析:
① 使用二维数组来存储迷宫,墙用 1 表示,路用 0 表示,如下图所示:
为与题目中的入口坐标 (1,1) 和出口坐标 (8,8) 对应,二维数组第 0 行和第 0 列不存储迷宫,用 1 填充。
② 对于任意一点(x,y),下一步都有前后左右四个可能的方向,即(x+1,y),(x-1,y),(x,y+1),(x,y-1),使用 fx[4] = { -1,1,0,0 }和 fy[4] = { 0,0,-1,1 } 来模拟下一步走向。
③ 搜索方式可以选择广度优先搜索(BFS) 或 深度优先搜索(DFS)。广度优先搜索利用了队列的先进先出性质,找到的路径是一条最短的通路;深度优先搜索利用了递归回溯的思想,所找到的路径不一定是一条最短通路。
2. 基于BFS搜索一条路径
使用数组来模拟队列操作实现广度优先搜索,设置队头队尾指示器qh和qe,通过这两个变量的增减来模拟入队和出队操作。
搜索过程如下图所示:
若其中某一步时队列为空(即qh==qe),说明该迷宫没有通路。
2.1 完整代码及注释
# include<stdio.h>
# define N 9
int maze[N][N] = {
{
1,1,1,1,1,1,1,1,1}, //迷宫,1表示墙,0表示路,入口为maze[1][1],出口为maze[8][8]
{
1,0,0,0,0,0,0,0,0}, //maze[0][0]至maze[0][8]和maze[0][0]至maze[8][0](即第一行和第一列)不在迷宫范围内,用1填充。
{
1,0,1,1,1,1,0,1,0},
{
1,0,0,0,1,1,0,1,0},
{
1,0,1,0,0,0,0,1,0},
{
1,0,1,0,1,1,0,1,0},
{
1,0,1,0,0,0,0,1,1},
{
1,0,1,0,0,1,0,0,0},
{
1,0,1,1,1,1,1,1,0}};
int fx[4] = {
-1,1,0,0 }, fy[4] = {
0,0,-1,1 }; //每个点的上下左右四个方向扩展
struct {
//存放路径坐标和前一个坐标的位置
int x, y, pre;
}sq[100];
int Check(int i, int j); //检查当前坐标是否可行
void Output(int qe); //输出路径坐标
/*模拟队列操作实现广度优先搜索*/
void Path_Search() {
int i, j, k, qh = 0, qe = 1;
maze[1][1] = -1; //置-1表示当前坐标已走过
sq[1].pre = 0;
sq[1].x = 1; //保存当前坐标
sq[1].y = 1;
while (qh != qe) {
//出现qh=qe则说明该迷宫走不通,跳出循环
qh = qh + 1;
for (k = 0; k <= 3; k++) {
//搜索当前坐标的四个扩展方向
i = sq[qh].x + fx[k];
j = sq[qh].y + fy[k];
if (Check(i, j) == 1) {
//检查是否可行
qe = qe + 1;
sq[qe].x = i;
sq[qe].y = j;
sq[qe].pre = qh;
maze[i][j] = -1;
if (sq[qe].x == 8 && sq[qe].y == 8) {
//走到(8,8)即出口则结束搜索,输出路径并返回
Output(qe);
return;
}
}
}
}
printf("未找到出路!\n"); //若循环结束未return则说明走不通
}
/*检查当前坐标是否可行*/
int Check(int i, int j) {
int flag = 1;
if (i < 1 || i > 8 || j < 1 || j > 8) //是否在迷宫内
flag = 0;
if (maze[i][j] == 1 || maze[i][j] == -1) //是否有路,是否走过
flag = 0;
return flag;
}
/*输出路径坐标*/
void Output(int qe) {
printf("一条通路为:\n");
int path_x[100], path_y[100], i = 2, j = 0;
path_x[1] = sq[qe].x;
path_y[1] = sq[qe].y;
while (sq[qe].pre != 0) {
//将sq中保存的路径保存到path_x和path_y中以便正序输出,若直接输出sq则是倒序
qe = sq[qe].pre;
path_x[i] = sq[qe].x;
path_y[i] = sq[qe].y;
i++;
}
for (i = i - 1; i >= 1; i--) {
//正序输出路径坐标
printf("(%d,%d) ", path_x[i], path_y[i]);
j++;
if (i != 1)
printf("-> ");
if (j % 5 == 0) //每五个一行
printf("\n");
}
}
int main() {
Path_Search();
return 0;
}
2.2 运行结果
3. 基于DFS搜索一条路径
利用递归和回溯的思想,若某一点的四个扩展方向都走不通,则回溯到上一个可行点。
由于每一点的下一步扩展是用数组 fx[4] = { -1,1,0,0 }和 fy[4] = { 0,0,-1,1 } 来存储的,因此在每一点的搜索顺序是上、下、左、右。
搜索过程如下图所示:
3.1 完整代码及注释
# include<stdio.h>
# define N 9
int maze[N][N] = {
{
1,1,1,1,1,1,1,1,1}, //迷宫,1表示墙,0表示路,入口为maze[1][1],出口为maze[8][8]
{
1,0,0,0,0,0,0,0,0}, //maze[0][0]至maze[0][8]和maze[0][0]至maze[8][0](即第一行和第一列)不在迷宫范围内,用1填充。
{
1,0,1,1,1,1,0,1,0},
{
1,0,0,0,1,1,0,1,0},
{
1,0,1,0,0,0,0,1,0},
{
1,0,1,0,1,1,0,1,0},
{
1,0,1,0,0,0,0,1,1},
{
1,0,1,0,0,1,0,0,0},
{
1,0,1,1,1,1,1,1,0} };
int fx[4] = {
-1,1,0,0 }, fy[4] = {
0,0,-1,1 }; //每个点的上下左右四个方向扩展
int Check(int i, int j, int k); //检查当前坐标是否可行
void Output(); //输出路径坐标
void Path_Search(int i, int j, int dep) {
int k, newi, newj;
for (k = 0; k <= 3; k++) {
//搜索当前坐标的四个扩展方向
if (Check(i, j, k) == 1) {
newi = i + fx[k];
newj = j + fy[k];
maze[newi][newj] = dep; //来到新位置后,设置当前值为搜索深度
if (newi == 8 && newj == 8) {
//走到(8,8)即出口则结束搜索,输出路径并返回
Output();
return;
}
else //否则进行下一层递归
Path_Search(newi, newj, dep + 1);
maze[newi][newj] = 0; //走不通,回溯
}
}
}
/*检查当前坐标是否可行*/
int Check(int i, int j, int k){
int flag = 1;
i = i + fx[k];
j = j + fy[k];
if (i < 1 || i > 8 || j < 1 || j > 8) //是否在迷宫内
flag = 0;
if (maze[i][j] != 0) //是否可行
flag = 0;
return flag;
}
/*输出路径坐标*/
void Output() {
printf("一条通路为:\n");
for (int i = 1; i <= 8; i++) {
for (int j = 1; j <= 8; j++) {
printf("%2d ", maze[i][j]);
}
printf("\n");
}
printf("该走法共 %d 步\n", maze[8][8] - 1);
}
int main() {
maze[1][1] = 2; //为不与用1表示的墙混淆,搜索深度从2开始,入口置2,输出时也从值为2的坐标点开始输出
Path_Search(1, 1, 3);
return 0;
}
3.2 运行结果
4. 基于DFS搜索所有可行路径
对上述DFS代码稍加修改,即在找到一条路径后不停止,继续回溯,回到上一个可行点继续寻找下一条路径,即可实现搜索所有可行路径。
4.1 完整代码及注释
/*迷宫问题_DFS实现全部路径搜索*/
# include<stdio.h>
# define N 9
int maze[N][N] = {
{
1,1,1,1,1,1,1,1,1}, //迷宫,1表示墙,0表示路,入口为maze[1][1],出口为maze[8][8]
{
1,0,0,0,0,0,0,0,0}, //maze[0][0]至maze[0][8]和maze[0][0]至maze[8][0](即第一行和第一列)不在迷宫范围内,用1填充。
{
1,0,1,1,1,1,0,1,0},
{
1,0,0,0,1,1,0,1,0},
{
1,0,1,0,0,0,0,1,0},
{
1,0,1,0,1,1,0,1,0},
{
1,0,1,0,0,0,0,1,1},
{
1,0,1,0,0,1,0,0,0},
{
1,0,1,1,1,1,1,1,0} };
int fx[4] = {
-1,1,0,0 }, fy[4] = {
0,0,-1,1 }; //每个点的上下左右四个方向扩展
int Check(int i, int j, int k); //检查当前坐标是否可行
void Output(); //输出路径坐标
int count = 0; //统计走法种数
void Path_Search(int i, int j, int dep) {
int k, newi, newj;
for (k = 0; k <= 3; k++) {
//搜索当前坐标的四个扩展方向
if (Check(i, j, k) == 1) {
newi = i + fx[k];
newj = j + fy[k];
maze[newi][newj] = dep; //来到新位置后,设置当前值为搜索深度
if (newi == 8 && newj == 8) {
//走到(8,8)即出口则结束搜索,输出路径并返回
count++;
Output();
}
else //否则进行下一层递归
Path_Search(newi, newj, dep + 1);
maze[newi][newj] = 0; //走不通,回溯
}
}
}
/*检查当前坐标是否可行*/
int Check(int i, int j, int k) {
int flag = 1;
i = i + fx[k];
j = j + fy[k];
if (i < 1 || i > 8 || j < 1 || j > 8) //是否在迷宫内
flag = 0;
if (maze[i][j] != 0) //是否可行
flag = 0;
return flag;
}
/*输出路径坐标*/
void Output() {
printf("第%d种走法为:\n", count);
for (int i = 1; i <= 8; i++) {
for (int j = 1; j <= 8; j++) {
printf("%2d ", maze[i][j]);
}
printf("\n");
}
printf("该走法共 %d 步\n\n", maze[8][8] - 1);
}
int main() {
maze[1][1] = 2; //为不与用1表示的墙混淆,搜索深度从2开始,入口置2,输出时也从值为2的坐标点开始输出
Path_Search(1, 1, 3);
return 0;
}
4.2 运行结果
更多算法内容关注我的《算法》专栏:https://blog.csdn.net/weixin_51450101/category_11590752.html?spm=1001.2014.3001.5482