目录
bool inputMap(vector>& matrix);
void setRandMatrix(vector>& matrix, int m, int n);
题目要求
大二上学期学的数据结构,期末老师让做课设,每人分配一题,我分到了下面这题,还算有趣
我开始想做纯图形界面的,用c#做,但是老师要求用c++,鉴于qt那么难用,还是控制台吧。。
图形库用的是easyx,使用参考了这篇文章:C/C++图形库EasyX快速上手指南【1】
easyx可以在官网下载:https://easyx.cn/
下载完直接安装,集成环境我用的vs,一键就安装好了
源码需要的话可以联系我
效果
控制台界面
导入地图(AUST,yeyeye!!!!!)
随机生成地图,可以自定义行数和列数
DFS(深度优先搜索)
BFS(广度优先搜索)
以下为代码讲解
main.cpp
#include <iostream>
#include <string>
#include<easyx.h>
#include<vector>
#include"Draw.h"//生成matrix和画图的函数在这个文件里面
#include"Search.h"//搜索的函数在这个文件里面
using namespace std;
int main()
{
for (;;) {
vector<vector<bool>> matrix;//声明迷宫的二维矩阵,类型为bool类型,true为通路,false为墙
/*这个matrix很重要嗷,生成、画图、寻路的函数都是关于这个矩阵的*/
welcome();//欢迎语
init(matrix);//生成矩阵的函数,能实现读取文件生成和随机生成
int c;
/*下面这段没封装起来,主要是我嫌麻烦,没弄了*/
to://用了goto,我发现在做选项的时候用goto真是好用
cout << "DFS请按1,BFS请按2" << endl;
cin >> c;
bool flag;
/*dfs和bfs返回的都是一个int型数组,DrawRedLine读这个数组然后把路径画在界面上*/
if (c == 1)
flag = DrawRedLine(dfs(matrix));
else if (c == 2)
flag = DrawRedLine(bfs(matrix));
else {
cout << "输入错误!请重新输入" << endl;
goto to;
}
if (flag)//标志位,寻路搜索完成之后会返回true和false,读取flag判断是否存在路径
cout << "路径生成完毕" << endl;
else
cout << "不存在路径" << endl;
if (getchar() == 'q') {
closegraph();
break;
}
}
}
Draw.cpp
void init(vector<vector<bool>>& matrix);
void init(vector<vector<bool>>& matrix) {
int input;
to1:
cout << "|导入地图请按1|随机生成地图请按2|" << endl;
cin >> input;
if (input == 1) {
if (!inputMap(matrix)) goto to1;//读取文件生成迷宫
}
else if (input == 2) {
cout << "请输入列数m=";
cin >> m;
if (m > 60) goto to2;
cout << "请输入行数n=";
cin >> n;
if (n > 60) goto to2;
setRandMatrix(matrix, m, n);//随机生成迷宫矩阵
}
else {
to2:
cout << "输入错误,请重新输入" << endl;
goto to1;
}
//这时候matrix已经赋值好力
//m和n均为全局变量,也已被赋值
wide = 620 / m < 440 / n ? 620 / m : 440 / n;//计算小方块宽度
/*
* wide是一个全局变量,因为在之后的生成路径图像中还需要用到
为了因为行列设置过多导致生成的界面过大或过小
进行了一些限制,小方块宽度=min(最大宽度/行数和列数)
*/
initgraph(wide * m, wide * n, 1);//生成窗口,
/*
界面长宽等于行数、列数x小方块宽度
最后的1是生成模式,这样控制台和界面才会共存
*/
setBlock(BLUE);//设定方块初始化
/*
* 简单封装了一下,可以生成任意颜色的方块
void setBlock(COLORREF color) {
setfillcolor(color);
setlinestyle(0, 2);//设置线框模式和宽度
setlinecolor(WHITE);//白色线框
}
*/
cout << "地图生成完毕!" << endl;
//读matrix,绘制到界面
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++) {
setfillcolor(matrix[i][j] ? BLUE : YELLOW);//true对应蓝方块,false对应黄方块
if ((i == 0 && j == 0) || (i == n - 1 && j == m - 2)) setfillcolor(GREEN);//起点终点对应绿方块
fillrectangle(j * wide, i * wide, wide + j * wide, wide + i * wide);//把(j,i)画在界面上
/*
* void fillrectangle (int left, int top, int right, int bottom);
4个参数分别是左上x,y,右下x,y
*/
}
}
其中包含两个函数,一个是读文件给matrix赋值,一个是随机生成
bool inputMap(vector<vector<bool>>& matrix);
bool inputMap(vector<vector<bool>>& matrix) {
string str;
ifstream ifile("map.txt", ios::in);
if (!ifile) {
cout << "找不到文件" << endl;
return false;
}
while (getline(ifile, str)) {
vector<bool> temp;
for (int i = 0; i < str.size(); i++) {
temp.push_back(str[i] == '1');
}
matrix.push_back(temp);
}
m = matrix[0].size();
n = matrix.size();
cout << m << n << endl;
ifile.close();
return true;
}
/*
读map.txt中的字符串,没什么好说的
记得#include<fstream>
*/
void setRandMatrix(vector<vector<bool>>& matrix, int m, int n);
void setRandMatrix(vector<vector<bool>>& matrix, int m, int n) {//随机生成迷宫
default_random_engine e;
bernoulli_distribution u(0.3);//修改这个可以增加墙壁生成的概率
e.seed(time(0));//生成随机数
/*
每次调用u(e)30%生成true,70%生成false
记得#include <random>
还有#include <ctime>
*/
//matrix中逐个赋值
for (int i = 0; i < n; i++) {
vector<bool> temp;
for (int j = 0; j < m; j++) {
if ((i == 0 && j == 0) || (i == n - 1 && j == m - 2)) temp.push_back(0);
else temp.push_back(u(e));
//这里值得注意,入口和出口直接赋值为0
}
matrix.push_back(temp);
}
}
此外还有一个DrawRedLine函数,负责画路径,这个因为和搜索比较紧密,在Search.cpp中讲解和展示
这样,界面和地图就生成好了,接下来就是搜索路径和画出路径
Search.cpp
这个文件中有两个函数,输入都是matrix的二维数组,输出都是int型的一维度数组,数组里存的是出口到入口的方向
为什么是出口到入口,和算法有关,我用了动态规划(dp),在看代码之前,先讲一下思路吧:
首先不管什么搜索,布尔型二维数组matrix先会被转换成int型的二维数组dp用于存放方向
dp初始化
void dpInit(vector<vector<bool>>& matrix, vector<vector<int>>& dp) {
for (int i = 0; i < matrix.size(); i++) {
vector<int> temp;
for (int j = 0; j < matrix[0].size(); j++)
temp.push_back(matrix[i][j] ? 1 : 0);
dp.push_back(temp);
}
}
我是这样宏定义的
#define UP 2
#define RIGHT 3
#define DOWN 4
#define LEFT 5
//还有就是0是路1是墙
然后这两个算法会在dp每一行里填入方向,如果有通路的话终点的格子一定会被填入方向,然后根据这个方向倒推(是下面就往上找,左边就往右找),找到的下一格也是这样,最后就能找到起点。
简单画了个图
建一个数组,每找一个格子就存进去,最后返回这个数组。
根据dp数组生成road数组
void roadPush(vector<vector<int>>& dp, vector<int>& road) {
int m = dp[0].size(), n = dp.size();
if (dp[n - 1][m - 2] != 0) {
int y = n - 1, x = m - 2;
while (x != 0 || y != 0) {
road.push_back(dp[y][x]);
//cout << dp[y][x];
switch (dp[y][x])
{
case UP: y++; break;
case DOWN:y--; break;
case RIGHT:x--; break;
case LEFT:x++; break;
default: break;
}
}
}
}
好了,接下来就是核心算法了,之所以这是数据结构课设,就是为了这个:
vector<int> dfs(vector<vector<bool>>& matrix);
void recursion(vector<vector<int>>& dp, int dir = 1, int x = 0, int y = 0) {
if (x == dp[0].size() || y == dp.size() || x < 0 || y < 0 || dp[y][x] != 0) return;
dp[y][x] = dir;
recursion(dp, RIGHT, x + 1, y);
recursion(dp, LEFT, x - 1, y);
recursion(dp, UP, x, y - 1);
recursion(dp, DOWN, x, y + 1);
}
vector<int> dfs(vector<vector<bool>>& matrix) {
vector<int> road;
vector<vector<int>> dp;//初始化dp矩阵
dpInit(matrix, dp);
recursion(dp);
roadPush(dp, road);
return road;
}
没什么好说的,dfs写多了真的很好写
上下左右挨个找,先判断这一格是否合理,不合理跳出递归,合理的话把上一次递归的方向给这一格,然后进入下一次递归
vector<int> bfs(vector<vector<bool>>& matrix);
为了方便先定义了一个结构体Point
typedef struct Point {
int x;
int y;
Point(int x, int y) {
this->x = x;
this->y = y;
}
};
vector<int> bfs(vector<vector<bool>>& matrix) {
vector<int> road;
int m = matrix[0].size(), n = matrix.size();
vector<vector<int>> dp;//初始化dp矩阵
dpInit(matrix, dp);
queue<Point> points;//建立队列,储存可扩展的点
points.push(Point(0, 0));
dp[0][0] = 1;
while (!points.empty()) {
int x = points.front().x, y = points.front().y;
bool dead = true; //标志位,若所有条件不满足则pop掉这个点
//找右边
if (x + 1 < m && dp[y][x + 1] == 0) {
points.push(Point(x + 1, y));
dp[y][x + 1] = RIGHT;
dead = false;
}
//找左边
if (x - 1 >= 0 && dp[y][x - 1] == 0) {
points.push(Point(x - 1, y));
dp[y][x - 1] = LEFT;
dead = false;
}
//找下面
if (y + 1 < n && dp[y + 1][x] == 0) {
points.push(Point(x, y + 1));
dp[y + 1][x] = DOWN;
dead = false;
}
//找上面
if (y - 1 >= 0 && dp[y - 1][x] == 0) {
points.push(Point(x, y - 1));
dp[y - 1][x] = UP;
dead = false;
}
if (dead) points.pop();
}
roadPush(dp, road);
return road;
}
bfs不会的话可以先去搜搜,这里就简单说一下思路
建立一个队列,先推入起点
循环,直到队列为空
队首的四周是否有路,有的话把这些点存到队尾,没有就推出这个点
画个图
最后是画图函数
bool DrawRedLine(vector<int> dir);
bool DrawRedLine(vector<int> dir) {
if (dir.empty()) return false;//数组为空说明不存在通路
setBlock(RED);//设置方块颜色为红
for (int i = 0, x = m - 2, y = n - 1; i < dir.size() - 1; i++) {
switch (dir[i])
{
case UP: y++; break;
case DOWN:y--; break;
case RIGHT:x--; break;
case LEFT:x++; break;
default: break;
}
fillrectangle(x * wide, y * wide, wide + x * wide, +wide + y * wide);
}
return true;
}