题目与要求
题目描述
以一个m×n的长方阵表示迷宫,0和1分别表示迷宫中的通路和障碍。设计一个程序,对任意设定的迷宫,求出所有从入口到出口的通路,或得出没有通路的结论。
设计要求
编写一个求解迷宫的非递归和递归程序。求得的通路以三元组(i,j,d)的形式输出,其中:(i,j)指示迷宫中的一个坐标,d表示走到下一坐标的方向。如:对于下列数据的迷宫,输出的一条通路为z(1,1,1),(1,2,2),(2,2,2),(3,2,3),(3,1,2),······。
总体设计
设计概述
经过查阅文献,翻阅资料可得知,本次课程设计所面临的问题,将选择DFS深度寻路算法和回溯算法加以实现。同时,我们这里也可以引入图形库,将程序设计成为一个自动走迷宫的程序运行效果,详细展现出迷宫寻路的可视化过程,帮助大家详细理解DFS深度寻路算法以及回溯法。
整体框架模块
详细设计
数据结构设计
数据结构选择:
栈、图
类的定义:
坐标点类。
图:
本问题为迷宫问题,需要通过图来描述迷宫的路况,所以需要用图这一数据结构。
表示方法:
int map ROWS = {
{1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,0,1},
{1,0,1,0,1,0,1,1},
{1,0,1,0,1,0,1,1},
{1,0,1,1,1,0,1,1},
{1,0,0,0,1,0,1,1},
{1,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1}
};
图将由二维数组(矩阵)来实现,里面充满0和1,0代表通路,1代表地图里面的障碍。
栈:
在寻路过程中需要记录路径的遍历情况,并考虑到当走错路径时需要删除错误的路径,从而选择栈这一数据结构是非常合适的。
表现形式:
stack<Point> St;
调用C++中STL标准库里面的头文件直接定义一个有关Point类的栈,用于Point数据类型的压栈和出栈。
点Point:
定义一个类Point,里面包含属性:行坐标x,列坐标y,方向指数dir(1:下;2:右;3:上;4:左)下一个要走的方向。
算法思想和模块设计
DFS深度寻路算法
该算法从图的某个顶点出发,沿着一个路径遍历图直到最后一个顶点,并在回溯时继续搜索其他路径,直到所有节点都被访问为止。DFS 通过利用栈来记录每个节点的状态,从而实现对图或树的遍历。在算法实现中,DFS 通常使用递归方法或显式栈实现。
回溯算法
该算法是一种用于求解决策问题的算法,它通过尝试所有可能的解决方案来找出最佳的解决方案。回溯算法通常适用于问题的解空间非常大,且没有直接的有效解法的情况。回溯算法按照深度优先的顺序尝试每一个可能的解决方案,如果当前的方案无法满足问题的约束条件,则回溯到上一个节点重新进行尝试。这个过程不断重复,直到找到问题的最终解或者确定不存在解决方案为止。
详细解题思路
了解到两种算法的核心概念和思维后,那么我们就可以详细设计解题思路了。
我们可以根据图深度优先搜索的算法思想,探索出一条起点到终点的路径,加上回溯,搜索出其他的路径,最终会在控制台上打印出所有的路径。
然后我们要实现程序算法的可视化:我们不难得知在C/C++语言中,里面内置一种<graphics.h>,需要将easyX配置到VS2022的编译器上,方可运行使用。编译环境搭建好以后,进行图形界面的设计,再到相关网站上下载一些合适的图片素材,利用贴图技术模拟。根据人的坐标变换,将每次循环的过程中都利用一遍贴图技术,这样根据人类视觉的延迟性,就会让我们觉得像放动画一样,将DFS寻路算法的过程清晰的展现在图形界面上。同时找到终点时,控制台也可以打印出相应的路径。
主函数代码设计
在得到上面的详细解题思路后我们可以采用自顶向下的编程思路,先对主函数进行编写:
int main() {
int count = 1;//用于记录路径条数
//1.创建地图
int map[ROWS][COLS] = {
{1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,0,1},
{1,0,1,0,1,0,1,1},
{1,0,1,0,1,0,1,1},
{1,0,1,1,1,0,1,1},
{1,0,0,0,1,0,1,1},
{1,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1}
};
//2.设置遍历数组,用于标记某点是否已经被走过
bool isWalk[ROWS][COLS] = { false };
//3.设置起点和终点
Point startPos = { 1,1,0 };
Point endPos = { 6,6,0 };
//3.建立一个栈,将起点压入栈中,并标记起点已走过
stack<Point> St;
St.push(startPos);
isWalk[startPos.x][startPos.y] = true;
Point searchPos;
bool isFindPath;//是否找到可走的方向
Init(map, St);//初始化图形界面
//4.DFS
while (!St.empty()){
Sleep(100);
putImg(St.top(), map);
searchPos = St.top();
//判断是否到达终点
if (searchPos.x == endPos.y && searchPos.y == endPos.y) {
stack<Point> path = stackCopy(St);
cout << "第" << count << "条路径:" << endl;
//控制台打印路径
printPath(path);
//图形界面打印路径
putRoadImg(St);
count++;
//进行回溯
Backtrack(searchPos, isWalk, St);
}
//调用寻路函数
searchPath(searchPos, isFindPath, St, map, isWalk);
if (isFindPath) {//找到路了
GO(searchPos, isWalk, St);//前进
}
else {
Back(isWalk, St);//后退
}
}
//5.关闭图形库总结遍历道路
closegraph();
if(count-1 == 0)
cout << "没有通路" << endl;
else
cout << "共" << count - 1 << "条道路,遍历结束" << endl;
return 0;
}
解题步骤就主要是以上的注释所呈现的,接下来我们将分模块介绍,每个函数模块或类。
(1)Point 类
struct Point//定义一个每个点的结构体
{
int x;
int y;
int dir = 1;//1.下 2.右 3.上 4.左
};
(2) Init函数
该函数主要用于初始化控制台界面和图形界面,将地图以图形界面的形式展现在屏幕上,并实行控制台交互,程序根据用户输入的指令判断是进行寻路还是退出程序。
//图形界面初始化
void Init(int map[ROWS][COLS], stack<Point> St) {
initgraph(ROWS * SPACE, COLS * SPACE, SHOWCONSOLE);//图形框初始化
loadImg();//加载图片
putImg(St.top(), map);
cout << "地图初始化完成" << endl;
cout << "是否开始寻路?是请输入1,否请输入0:";
int choice;
cin >> choice;
while (choice != 1 && choice != 0) {
cout << "请重新输入:";
cin >> choice;
}
if (choice == 1) {
system("cls");
cout << "道路遍历开始!!!" << endl;
return;
}
else {
system("cls");
cout << "已退出程序" << endl;
exit(0);
}
}
(3)putImg函数
用于在指定的位置贴上指定的图片。
动画模拟:因为人的视觉有延迟功能,位置都在每次循环而改变,所以每次循环贴图就可以模拟出一种动画的效果。
//贴图函数
void putImg(Point pos, int map[ROWS][COLS]) {
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
if (pos.x == i && pos.y == j) {
putimage(j * SPACE, i * SPACE, &people);
}
else if (map[i][j] == 0) {
putimage(j * SPACE, i * SPACE, &road);
}
else {
putimage(j * SPACE, i * SPACE, &wall);
}
}
}
}
(4)printPath函数
用于输出到达终点后,对栈中的路径进行在控制台的输出。
void printPath(stack<Point> Path) {//用于输出路径
while (!Path.empty())
{
cout << "(" << Path.top().x << "," << Path.top().y << "," << Path.top().dir << ")" << "->";
Path.pop();
}
cout << "\b\b " << endl;
}
(5) putRoadImg函数
在找到终点后在图形界面上打印足迹。
//路径贴图函数
void putRoadImg(stack<Point> St) {
stack<Point> temp = St;
while (!temp.empty()) {
putimage(temp.top().y * SPACE + SPACE / 4, temp.top().x * SPACE + SPACE / 4, &pos);
temp.pop();
}
Sleep(6000);
}
(6)Backtrack函数
到达终点后,坐标点进行回溯,寻找其他路径。
//回溯函数
void Backtrack(Point& searchPos, bool isWalk[ROWS][COLS], stack<Point>& St) {
isWalk[St.top().x][St.top().y] = false;
St.pop();
searchPos = St.top();
}
(7) searchPath函数
上下左右寻找通路。
//寻路函数
void searchPath(Point& searchPos, bool& isFindPath,stack<Point> &St,int map[ROWS][COLS],bool isWalk[ROWS][COLS]) {
isFindPath = false;
while (searchPos.dir <= 4 && isFindPath == false) {
searchPos.dir++;
//试探每一个方向
switch (searchPos.dir) {
case 1://下
searchPos.x = St.top().x + 1;
searchPos.y = St.top().y;
break;
case 2://右
searchPos.x = St.top().x;
searchPos.y = St.top().y + 1;
break;
case 3://上
searchPos.x = St.top().x - 1;
searchPos.y = St.top().y;
break;
case 4://左
searchPos.x = St.top().x;
searchPos.y = St.top().y - 1;
break;
}
if (map[searchPos.x][searchPos.y] == 0 && isWalk[searchPos.x][searchPos.y] == false) {
isFindPath = true;
}
}
}
(8)GO函数
若找到了可走的路,就让角色前进。
//前进函数
void GO(Point & searchPos, bool isWalk[ROWS][COLS], stack<Point>&St) {
St.top().dir = searchPos.dir;
St.push(searchPos);
St.top().dir = 0;
isWalk[searchPos.x][searchPos.y] = true;
}
9.Back函数
找不到可以走的路了,就退栈,倒退到上一个结点。
//后退函数
void Back(bool isWalk[ROWS][COLS], stack<Point>& St) {
isWalk[St.top().x][St.top().y] = false;//当前路径标记为未走过
St.pop();
}
运行结果
不断寻找路径,不断在控制台上打印。
好!!!一个自动走迷宫,模拟DFS深度寻路算法的程序就这样做完了,想要源码和素材,记得点赞+关注并私信基诺领取哦。关注基诺,你会有更多意想不到的惊喜哦。