【数据结构课程设计】 迷宫问题
做完课设在B站发了一个演示视频,一直说写个博客总结记录一下但是一直在忙别的课设和考试就一直没做,现在终于考完试了那就总结一下吧~~
迷宫问题描述:
问题描述
心理学家把一只老鼠从一个无顶盖的大盒子的入口处放入,让老鼠自行找到出口出来。迷宫中设置很多障碍阻止老鼠前行,迷宫唯一的出口处放有一块奶酪,吸引老鼠找到出口。
迷宫四周设为墙;无填充处为可通处。设每个点有四个可通方向,分别为东、南、西、北。左上角为入口,右下角为出口。迷宫有一个入口,一个出口。设计程序求解迷宫的一条通路。
设计要求
(1)设计迷宫的存储结构。
(2)设计通路的存储结构。
(3)设计求解通路的算法。
(4)设计迷宫显示和通路的显示方式。
(5)输入:迷宫、入口及出口可在程序中设定,也可从键盘输入。
(6)输出:迷宫、入口、出口及通路路径。
思考:
(1)若每个点有8个试探方向(东、东南、南、西南、西、西北、北、东北),如何修改程序?
(2)如何求得所有通路?
(3)如何求得最短通路?
解决方案
这里要求迷宫的通路,我设计的是求最短通路和所有通路。
求最短通路的算法主要用到广度优先算法(bfs),由于bfs每次访问的都是与当前的点相邻的点,即发散型地访问遍历,每次访问到新的点都将该点距离起点的距离变为发出点距离起点的距离加一,并且入队,重复取出队头元素执行重复操作直到队列为空,即可求出每个点距离起点的最短距离。至于路径的输出,先求得所有点距离终点的最短距离dist[i][j](预处理),再从起点开始遍历,每次遍历只需要判断当前点距离终点的最短距离dist[i][j]是否等于下一个点(k,m)距离终点的最短距离dist[k][m] + 1,如果是,则这个点就是起点到终点通路上的一个点,可以用一个容器记录下来。
第十届蓝桥杯有到差不多的题跟这个求最短路的思路差不多,感兴趣的可以去看看。
求所有通路的算法主要用到深度优先算法(dfs), dfs是一种不撞南墙不回头的方法,当然,撞了南墙就要回头(回溯),只要下一个点能走就一直走,直到不能走就回头继续探索下一个点,是一种比较“暴力”的方法。当然,由于dfs本质上还是递归,所以要注意递归结束的条件。至于路径的记录,可以开一个数组或者vector来记录路径,每次走到一个点时,就将这个点添加到数组中,再将记录路径长度的变量加一,并将这个点置为已访问,当然,递归结束开始回溯时要恢复原本的状态(将记录路径长度的变量减一,该点未被访问)。
至于输出,我用来座标和图形化两种方式输出,座标就不多说了,图形化用到了EasyX库,根据每个点的座标来画出路径和迷宫即可。
核心算法都并不困难,但也需要注意细节,理解透彻,比如我写dfs的时候就因为没有注意递归出口出了bug,递归的结束条件真的是很重要啊!
主要代码如下:
源.cpp文件:
#include "wlx_Maze.h"
using namespace std;
int main()
{
Wlx_Maze m;
while (true)
{
m.menu();
int choice;
cin >> choice;
switch (choice)
{
case 1:
// 读入迷宫
m.read_maze();
break;
case 2:
m.second_menu();
break;
case 0:
exit(0);
default:
cout << "输入有误请重新输入!" << endl;
break;
}
system("pause");
system("cls");
}
return 0;
}
wlx_Maze.cpp文件:
#include "wlx_Maze.h"
int dir_row[4] = { -1, 1, 0, 0 },
dir_col[4] = { 0, 0, -1, 1 };// 上下左右四个方向
int dir1_row[8] = { -1, 1, 0, 0, -1, 1, -1, 1 },
dir1_col[8] = { 0, 0, -1, 1, -1, -1, 1, 1 }; // 上下左右,左上、左下、右上、右下八个方向
int mode; // 选择的模式
// 一级菜单
void Wlx_Maze::menu()
{
cout << "********************* wlx_迷宫求解 ************************" << endl;
cout << "********************* 1、重录迷宫 *************************" << endl;
cout << "********************* 2、已有迷宫 *************************" << endl;
cout << "********************* 0、退出 **************************" << endl;
cout << "请输入您的选择:";
}
// 二级菜单
void Wlx_Maze::second_menu()
{
if (this->col == 0)
{
cout << "暂无迷宫信息!请先录入!" << endl;
return;
}
bool flag = true;
while (true)
{
system("cls");
cout << "********************** wlx_迷宫求解 *************************" << endl;
cout << "********************* 1、显示迷宫构造 ***********************" << endl;
cout << "******************* 2、显示迷宫最短通路 *********************" << endl;
cout << "******************* 3、显示迷宫所有通路 *********************" << endl;
cout << "************************ 0、退出 ****************************" << endl;
cout << "请输入您的选择:";
int op;
cin >> op;
switch (op)
{
case 1:
// 显示迷宫
this->draw_maze(true, true);
break;
case 2:
// 显示最短路径
this->show_min_path();
break;
case 3:
// 显示所有路径
this->show_all_path();
break;
case 0:
// 退出
flag = false;
break;
default:
cout << "输入有误请重新输入!" << endl;
break;
}
if (!flag)
break;
system("pause");
}
}
// 初始化
void Wlx_Maze::init()
{
this->col = 0;
this->row = 0;
this->end_x = this->end_y = 0;
this->start_x = this->start_y = 0;
this->path_num = 0;
this->all_path_num = 0;
memset(this->each_path_num, 0, sizeof(this->each_path_num));
memset(this->dist, -1, sizeof(this->dist)); // 初始化每个点到终点的距离都为-1
memset(this->is_visited, false, sizeof(this->is_visited)); // 初始条件所有点均未被访问
}
// 构造函数
Wlx_Maze::Wlx_Maze()
{
// 初始化
this->init();
ifstream ifs;
ifs.open(MAZEFILE, ios::in);
// 打开文件失败
if (!ifs.is_open())
{
ifs.close();
return;
}
// 初始化
int r = 0;
while (ifs >> this->maze[r])
{
this->col = this->maze[r].length();
r++;
}
this->row = r;
//cout << "l = " << this->len << " w = " << this->wid << endl;
ifs.close();
}
// 获取迷宫
void Wlx_Maze::read_maze()
{
cout << "请输入迷宫的行数:";
cin >> this->row;
cout << "请输入迷宫的列数:";
cin >> this->col;
cout << "请输入迷宫:(提示:0为可走, 1为不可走)" << endl;
ofstream ofs;
ofs.open(MAZEFILE, ios::out);
for (int i = 0; i < this->row; i++)
{
cin >> this->maze[i];
ofs << this->maze[i] << endl; // 写入文件
}
ofs.close();
cout << "录入迷宫成功! 是否显示迷宫?(1、显示 2、不显示)" << endl;
int sel;
cin >> sel;
if (sel == 1)
this->draw_maze(true, true);
}
// 获取起点和终点的行号和列号
void Wlx_Maze::get_start_end()
{
while (true)
{
cout << "请输入迷宫起点的行号[0-" << this->row << "): " << endl;
cin >> this->start_x;
cout << "请输入迷宫起点的列号[0-" << this->col << "): " << endl;
cin >> this->start_y;
cout << "请输入迷宫终点的行号[0-" << this->row << "): " << endl;
cin >> this->end_x;
cout << "请输入迷宫终点的列号[0-" << this->col << "): " << endl;
cin >> this->end_y;
// 起点或终点为1则不合法
if ( this->start_x < 0 || this->end_x >= this->row
|| this->start_y < 0 || this->end_y >= this->col
|| this->maze[this->start_x][this->start_y] == '1'
|| this->maze[this->end_x][this->end_y] == '1')
{
cout << "终点 / 起点不合法! 请重新输入!" << endl;
}
else
break;
}
}
// 获取每个点到终点的最短距离
void Wlx_Maze::get_min_distance()
{
memset(this->dist, -1, sizeof(this->dist));
cout << "您想要选择:1、模式1(只可走上下左右四个方向)" << endl <<
" 2、模式2(可走上下左右,左上,左下等八个方向, 最多 " << MAXSIZE << "条)" << endl;
cin >> mode;
// 定义队列
queue< pair<int, int> > q;
this->dist[this->end_x][this->end_y] = 0; // 终点到自己的距离为0
q.push({ this->end_x, this->end_y }); // 入队
while (!q.empty())
{
auto t = q.front();
q.pop();
// 模式1
if (mode == 1)
{
for (int i = 0; i < 4; i++)
{
// 新的行列
int new_r = t.first + dir_row[i];
int new_c = t.second + dir_col[i];
// 判断新的行列是否合法
if (new_r >= 0 && new_r < this->row
&& new_c >= 0 && new_c < this->col // 新点在迷宫范围内
&& this->dist[new_r][new_c] == -1 // 未被访问
&& this->maze[new_r][new_c] == '0') // 可走
{
// 新的距离等于当前距离加一
this->dist[new_r][new_c] = this->dist[t.first][t.second] + 1;
q.push({ new_r, new_c }); // 入队
}
}
}
// 模式2
else
{
for (int i = 0; i < 8; i++)
{
// 新的行列
int new_r = t.first + dir1_row[i];
int new_c = t.second + dir1_col[i];
// 判断新的行列是否合法
if (new_r >= 0 && new_r < this->row
&& new_c >= 0 && new_c < this->col // 新点在迷宫范围内
&& this->dist[new_r][new_c] == -1 // 未被访问
&& this->maze[new_r][new_c] == '0') // 可走
{
// 新的距离等于当前距离加一
this->dist[new_r][new_c] = this->dist[t.first][t.second] + 1;
q.push({ new_r, new_c }); // 入队
}
}
}
}
}
// 获取起点到终点的路径
void Wlx_Maze::get_min_path()
{
int sx = this->start_x, sy = this->start_y; // 起点座标
int ex = this->end_x, ey = this->end_y; // 终点座标
while (sx != ex || sy != ey) // 未到终点
{
if (mode == 1)
{
// 四个方向搜索
for (int i = 0; i < 4; i++)
{
// 新的座标
int nx = sx + dir_row[i];
int ny = sy + dir_col[i];
// 判断新座标是否合法
if ( nx >= 0 && nx < this->row
&& ny >= 0 && ny < this->col // 新点在地图范围内
&& this->maze[nx][ny] == '0' ) // 新点可走
{
// 如果当前点距离终点的距离等于新点距离终点的距离+1则为该条通路的路径
if (this->dist[sx][sy] == this->dist[nx][ny] + 1)
{
sx = nx, sy = ny; // 更新
// 记录路径
this->min_path[this->path_num].r = nx;
this->min_path[this->path_num++].c = ny;
}
}
}
}
else
{
for (int i = 0; i < 8; i++)
{
// 新的座标
int nx = sx + dir1_row[i];
int ny = sy + dir1_col[i];
// 判断新座标是否合法
if ( nx >= 0 && nx < this->row
&& ny >= 0 && ny < this->col // 新点在地图范围内
&& this->maze[nx][ny] == '0') // 新点可走
{
if (this->dist[sx][sy] == this->dist[nx][ny] + 1)
{
sx = nx, sy = ny;// 更新
// 记录路径
this->min_path[this->path_num].r = nx;
this->min_path[this->path_num++].c = ny;
}
}
}
}
}
}
// 画路径
void Wlx_Maze::draw_min_path()
{
this->draw_maze(false, true);
// 画起点
setfillcolor(RGB(212, 68, 68));
solidcircle((2 * this->start_y + 1) * (ROW / 2), (2 * this->start_x + 1) * (ROW / 2), RADIUS);
for (int i = 0; i < this->path_num; i++)
{
int new_r = this->min_path[i].r;
int new_c = this->min_path[i].c;
setfillcolor(RGB(R, G, B));
solidcircle((2 * new_c + 1) * (ROW / 2), (2 * new_r + 1) * (ROW / 2), RADIUS);
Sleep(DELAYTIME);
}
// 画终点
setfillcolor(RGB(176, 58, 223));
solidcircle((2 * this->end_y + 1) * (ROW / 2), (2 * this->end_x + 1) * (ROW / 2), RADIUS);
_getch();
closegraph();
}
void Wlx_Maze::draw_maze(bool is_quit, bool is_init) // 画迷宫
{
if (is_init)
{
// 创建绘图窗口
initgraph(WIDTH, HEIGHT);
// 设置背景色
setbkcolor(RGB(128, 128, 128));
// 用背景色清空屏幕
cleardevice();
}
for (int i = 0; i < this->row; i++)
{
for (int j = 0; j < this->col; j++)
{
if (this->maze[i][j] == '1') // 不可走
{
setfillcolor(RGB(125, 160, 214));
solidrectangle(j * ROW, i * COL, (j + 1) * ROW, (i+1) * COL);
}
else
{
setfillcolor(RGB(127, 243, 204));
solidrectangle(j * ROW, i * COL, (j + 1) * ROW, (i + 1) * COL);
}
}
}
// 按任意键退出
if (is_quit)
{
_getch();
closegraph();
}
}
// 显示迷宫最短通路
void Wlx_Maze::show_min_path()
{
// 获取起点和终点的座标
this->get_start_end();
// 初始化
this->path_num = 0;
// 获取每个点到终点的最短距离
this->get_min_distance();
// 无法到达终点
if (this->dist[this->start_x][this->start_y] == -1 )
{
cout << "终点不可达!" << endl;
return;
}
// 获取最短路径
this->get_min_path();
cout << "您想要输出迷宫的图像or路径座标?(1、图像 2、座标)" << endl;
int sel;
cin >> sel;
if(sel == 1) // 显示图像
this->draw_min_path();
else
{
cout << "row = " << this->start_x
<< " col = " << this->start_y << endl;
for (int i = 0; i < this->path_num; i++)
cout << "row = " << this->min_path[i].r
<< " col = " << this->min_path[i].c << endl;
}
}
// 获取所有通路的路径
Maze_Point p[MAXSIZE]; // 记录路径
int p_num = 0;
void Wlx_Maze::get_all_path(int current_row, int current_col)
{
// 到达终点
if (current_row == this->end_x && current_col == this->end_y)
{
if (this->all_path_num >= MAXSIZE) // 路径个数不要超过记录路径数组的最大值
return;
for (int i = 0; i < p_num; i++)
this->all_path[this->all_path_num][i] = p[i];
this->each_path_num[this->all_path_num] = p_num;
this->all_path_num++;
}
// 当前位置合法
if (current_row >= 0 && current_row < this->row
&& current_col >= 0 && current_col < this->col)
{
if (mode == 1) // 模式1
{
for (int i = 0; i < 4; i++)
{
// 新位置
int n_r = current_row + dir_row[i];
int n_c = current_col + dir_col[i];
// 判断新位置是否合法
if (n_r >= 0 && n_r < this->row
&& n_c >= 0 && n_c < this->col // 在迷宫范围内
&& !this->is_visited[n_r][n_c] // 未被访问过
&& this->maze[n_r][n_c] == '0') // 可走
{
this->is_visited[n_r][n_c] = true; // 已走
Maze_Point t;
t.r = n_r;
t.c = n_c;
p[p_num++] = t; // 记录路径
this->get_all_path(n_r, n_c); // 递归
// 回溯
this->is_visited[n_r][n_c] = false;
p_num--;
}
}
}
else
{
for (int i = 0; i < 8; i++)
{
// 新位置
int n_r = current_row + dir1_row[i];
int n_c = current_col + dir1_col[i];
// 判断新位置是否合法(在迷宫范围内、未被访问过、可走)
if (n_r >= 0 && n_r < this->row
&& n_c >= 0 && n_c < this->col // 在迷宫范围内
&& !this->is_visited[n_r][n_c] // 未被访问过
&& this->maze[n_r][n_c] == '0') // 可走
{
this->is_visited[n_r][n_c] = true; // 已走
Maze_Point t;
t.r = n_r;
t.c = n_c;
p[p_num++] = t; // 记录路径
this->get_all_path(n_r, n_c); // 递归
// 回溯
this->is_visited[n_r][n_c] = false;
p_num--;
}
}
}
}
}
// 显示迷宫的所有通路
void Wlx_Maze::show_all_path()
{
// 初始化操作
this->all_path_num = 0;
memset(this->each_path_num, 0, sizeof(this->each_path_num));
memset(this->is_visited, false, sizeof(this->is_visited)); // 初始条件所有点均未被访问
p_num = 0;
// 获取起点和终点座标
this->get_start_end();
p[p_num].r = this->start_x;
p[p_num++].c = this->start_y;
this->is_visited[this->start_x][this->start_y] = true;
cout << "您想要选择:1、模式1(只可走上下左右四个方向)" << endl <<
" 2、模式2(可走上下左右,左上,左下等八个方向, 最多 " << MAXSIZE << "条)"<< endl;
cin >> mode;
this->get_all_path(this->start_x, this->start_y);
cout << "您想要输出迷宫的图像or路径座标?" << endl
<< "1、图像(红色为起点,紫色为终点,按'a'上一页 、's'直接退出 、回车下一页) " << endl
<< "2、座标 " << endl;
int sel;
cin >> sel;
if (sel == 1)
{
// 画路径
this->draw_all_path();
}
else
{
for (int i = 0; i < this->all_path_num; i++)
{
cout << "第 " << i + 1 << "条通路为:" << endl;
for (int j = 0; j < this->each_path_num[i]; j++)
cout << "row = " << this->all_path[i][j].r
<< " col = " << this->all_path[i][j].c << endl;
cout << endl;
}
}
}
// 画出迷宫的所有通路
void Wlx_Maze::draw_all_path()
{
bool flag = true;
while (flag)
{
initgraph(WIDTH, HEIGHT);
setbkcolor(RGB(128, 128, 128)); // 背景颜色
cleardevice();
for (int i = 0; i < this->all_path_num; i++)
{
setbkcolor(RGB(128, 128, 128));
cleardevice();
this->draw_maze(false, false);
// 显示 当前页数/总页数
char page[19];
sprintf(page, "%d / %d", i + 1, this->all_path_num);
RECT r = { WIDTH - 2 * ROW, HEIGHT - COL, WIDTH, HEIGHT };
drawtext(_T(page), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
for (int j = 0; j < this->each_path_num[i]; j++)
{
// 获取路径所在的行列
int new_r = this->all_path[i][j].r;
int new_c = this->all_path[i][j].c;
// 画图
setfillcolor(RGB(R, G, B));
solidcircle((2 * new_c + 1) * (ROW / 2), (2 * new_r + 1) * (ROW / 2), RADIUS);
// 延时
Sleep(DELAYTIME);
}
// 画起点和终点
setfillcolor(RGB(212, 68, 68));
solidcircle((2 * this->start_y + 1) * (ROW / 2), (2 * this->start_x + 1) * (ROW / 2), RADIUS);
setfillcolor(RGB(176, 58, 223));
solidcircle((2 * this->end_y + 1) * (ROW / 2), (2 * this->end_x + 1) * (ROW / 2), RADIUS);
char key = _getch();
//cout << " i = " << i << endl;
if (key == 'a' || key == 'A') // 向左翻页
{
i -= 2;
if (i < 0)
i = -1;
}
else if (key == 's' || key == 'S') // 退出
{
// cout << key << endl;
flag = false;
closegraph();
break;
}
}
closegraph();
}
}
wlx_Maze.h头文件:
#define _CRT_SECURE_NO_DEPRECATE
#pragma once
#include <cstdio>
#include <cstring>
#include <cmath>
#include <string>
#include <map>
#include <queue>
#include <stack>
#include <iostream>
#include <algorithm>
#include <fstream>
#include <graphics.h>
#include <conio.h>
using namespace std;
const int MAXSIZE = 109;
#define ROW 30
#define COL 30
#define WIDTH 640
#define HEIGHT 480
#define DELAYTIME 200
#define R 212
#define G 144
#define B 186
#define RADIUS 10
#define MAZEFILE "maze_file.txt"
class Maze_Point // 迷宫上每个点的座标
{
public:
int r;
int c;
};
class Wlx_Maze // 迷宫
{
public:
int col; // 迷宫的列
int row; // 迷宫的行
int end_x, end_y; // 终点座标
int start_x, start_y; // 起点座标
string maze[MAXSIZE]; // 存放迷宫
int path_num; // 最短路径的长度
int all_path_num; // 通路的个数
Maze_Point all_path[MAXSIZE][MAXSIZE]; // 第i条通路对应的每个点的座标
bool is_visited[MAXSIZE][MAXSIZE]; // 判断是否走过
int each_path_num[MAXSIZE * 10]; // 每条通路的长度
int dist[MAXSIZE][MAXSIZE]; // 点i, j 到终点的最短距离
Maze_Point min_path[MAXSIZE * MAXSIZE];
void menu(); // 一级菜单
void second_menu(); // 二级菜单
Wlx_Maze(); // 构造函数
void init(); // 初始化
void read_maze(); // 获取迷宫
void get_start_end(); // 获取起点和终点的行号和列号
void get_min_distance(); // 获取每个点到终点的最短距离
void get_min_path(); // 获取起点到终点的路径
void show_min_path(); // 显示迷宫最短通路
void draw_min_path(); // 画最短通路路径
void get_all_path(int current_row, int current_col); // 获取所有通路的路径
void show_all_path(); // 显示迷宫的所有通路
void draw_all_path(); // 画出迷宫的所有通路
void draw_maze(bool is_quit, bool is_init); // 画迷宫
};
若有不足,欢迎指正~~