本文作者:涂欢
开发环境及语言:Dev-C++ 、 EasyX 、 C++
关键词:栈、深度优先、迷宫、可视化、静态迷宫、动态迷宫、复杂度、递归
文章目录
前言
学习完《数据结构与算法》后,学校安排了一门专业选修课《经典算法的设计与实现》,我的选题是“迷宫求解”,因为我自己的专业是数字媒体技术,偏游戏方向,同时也希望在做项目的时候会比较有趣,所以选了迷宫游戏。以下是我从项目结题报告中截取的一些内容。完整的报告我也上传了压缩包,里面包含从开题、设计、到结题的所有文件,需要的可以自行下载,如果是本校的选了这个课题那恭喜你发现宝藏咯!
一、问题描述
主要利用栈实现,要求静态迷宫能准确找到路径,且能动态生成迷宫,显示所有路径。用图形界面显示所找到的路径。
二、开发环境及工具
- C++(推荐课程:黑马程序员C++教程)
- Dev-C++(当时本来是想用VS的,因为考虑到EasyX图形库配置VS非常的方便,奈何我的轻薄本带不动,运行个简单的程序都要几分钟,所以只能继续使用Dev-C++,至少不卡,于是在CSDN查找教程,看过很多,最后遇到了一个比较简单且最后配置成功的文章C++EasyX教程(一)-如何在DEV-C++里安装EasyX库)
- EasyX图形库(推荐课程:C/C++/EasyX图形编程)
三、项目设计
1、思路分析
这里直接给出我当时的主要的三个参考文章或视频:
2、数据类型
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
3、核心算法
- 深度优先搜索(DFS)
- 回溯法
四、源代码
//----------------------------------------库文件 --------------------------
#include <iostream>
#include<stdio.h>
#include <graphics.h> // 引用图形库头文件
#include <conio.h> //使用_getch();函数头文件
#include <stack> //stack是STL(标准模板库)提供的容器之一
#include <Windows.h>
#include<time.h>//使用系统时间来初始化
#include<math.h>
#include<stdlib.h>//使用srand函数
using namespace std;
//-------------------------------------- 宏定义---------------------------
// 地图值: 1是墙壁,0是通道,2~5是主角的方向(上下左右),6是出口
#define WALL 1
#define PASS 0
#define UP 2
#define DOWN 3
#define LEFT 4
#define RIGHT 5
#define EXITS 6
//---------------------------------全局变量-----------------------------
int m = 10, n = 10; // 行和列 默认为10
int map[100][100]; // 地图数组,先开大一点,根据m、n限定范围
int vis[100][100]; // 是否访问过,0是没访问,-1是访问过了
bool isFind = false; // 是否找到出口
stack<int(*)> pathStack;// 用STL模板的栈类记录路径
//-----------------------------------加载图片-------------------------
IMAGE imgs[10];
void LoadImags() {
loadimage(&imgs[0], "lu.png", 80, 80);
loadimage(&imgs[1], "qiang.png", 80, 80);
loadimage(&imgs[2], "u.png", 80, 80);
loadimage(&imgs[3], "d.png", 80, 80);
loadimage(&imgs[4], "l.png", 80, 80);
loadimage(&imgs[5], "r.png", 80, 80);
loadimage(&imgs[6], "qiang.png", 80, 80);
}
//-----------------------------------绘制地图--------------------------
void DrawMap() {
Sleep(300); // 休眠0.3秒,更新图(300ms)
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++)
{
putimage(j * 80, i * 80, &imgs[map[i][j]]);
}
}
}
//------------------------辅助函数,当前方向的对立方向-----------------
int GetAnotherDir(int dir) {
if (dir == 2) {
return 3;
}
if (dir == 3) {
return 2;
}
if (dir == 4) {
return 5;
}
if (dir == 5) {
return 4;
}
return dir;
}
/*
思路:采用深度优先搜索
递归中自带“栈特性”,在进入当前递归函数相当于栈的push,退出当前递归函数相当于栈的pop
那么根据这个特性,实现搜索地图中返回上一个结点的功能。
*/
//---------------------------------------------寻找通路的函数-----------------------------------------------
void DFS(int cury, int curx, int dir, int lasty, int lastx, int ysval) {// cury curx 表示当前点,dir是当前位置的方向, lasty,lastx 表示上一个点,ysval表示上一个点的地图值
if (isFind) {// 找到了出口,递归栈都返回
return;
}
if (vis[cury][curx] == -1 || map[cury][curx] == 1 || cury >= m || cury < 0 || curx >= n || curx < 0) {// 递归返回条件:这个位置访问过,墙壁,越界,
return;
}
if (map[cury][curx] == EXITS) {// 递归退出条件,找到了出口
//cout << "找到了出口" << endl;
isFind = true;
return;
}
// 递归函数执行时,执行相当于栈push时的操作
pathStack.push(new int[3]{ cury, curx, dir });// 记录当前路径和方向
vis[cury][curx] = -1; // 表示访问过当前点
int curysval = map[cury][curx]; // 保存当前的地图值
// 更新主角所在位置。
map[lasty][lastx] = ysval; // 上一个所在的位置变回地图值
map[cury][curx] = dir; // 主角现在所在的位置 = 主角面向方向值
DrawMap(); // 递归函数执行时,当前的地图值
// 上下左右四个方向探索,并传入当前位置和当前的地图值(以便在下一个递归函数把当前主角所占的位置变回地图值)
DFS(cury + 1, curx, DOWN, cury, curx, curysval);// 先向下边探索,这里先往下搜索,对比一下先向右边探索
DFS(cury, curx + 1, RIGHT, cury, curx, curysval);// 向右边探索
DFS(cury, curx - 1, LEFT, cury, curx, curysval);// 向左边探索
DFS(cury - 1, curx, UP, cury, curx, curysval);// 向上边探索
if (isFind) {// 找到了出口,现在的路径栈保存了正确的访问路径,退出即可,但是这个路径可能不是最优路径
return;
}
// 4个方向都走完了,无路可走,需要返回上一个位置
// 递归函数结束时,相当于栈pop时的操作
// 需要把递归函数进入时改变的数值等操作回归原位
//vis[cury][curx] = 0; // 当前点没访问过,去掉没影响,但恢复为0可能可以找到最优路径
pathStack.pop(); // 舍去记录当前位置
map[cury][curx] = curysval; // 现在所在的位置变回一开始的地图值
map[lasty][lastx] = GetAnotherDir(dir); // 主角返回到上一个位置,上一个位置的方向,是当前对立的方向才能绘图正确
DrawMap(); // 绘制当前的地图值
}
//------------------------------------------------根据栈保存的路径值,修改地图值----------------------------------------------------
void UpdateMapToPath() {
while (!pathStack.empty()) {
int* pos = pathStack.top();
pathStack.pop();
map[*pos][*(pos + 1)] = *(pos + 2); // 变成主角当时的方向值
}
}
//------------------------------------------------------迷宫初始化---------------------------------------------------------
void InitMaze(int Rank2,int x, int y)
{
//墙和路径的标识
int BAR = 0 ;
int ROUTE = 1 ;
map[x][y] = ROUTE;
//确保四个方向随机
int direction[4][2] = { { 1,0 },{ -1,0 },{ 0,1 },{ 0,-1 } };
for (int i = 0; i < 4; i++) {
int r = rand() % 4;
int temp = direction[0][0];
direction[0][0] = direction[r][0];
direction[r][0] = temp;
temp = direction[0][1];
direction[0][1] = direction[r][1];
direction[r][1] = temp;
}
//向四个方向开挖
for (int i = 0; i < 4; i++) {
int dx = x;
int dy = y;
//控制挖的距离,由Rank来调整大小
int range = 1 + (Rank2 == 0 ? 0 : rand() % Rank2);
while (range>0) {
dx += direction[i][0];
dy += direction[i][1];
//排除掉回头路
if (map[dx][dy] == ROUTE) {
break;
}
//判断是否挖穿路径
int count = 0;
for (int j = dx - 1; j < dx + 2; j++) {
for (int k = dy - 1; k < dy + 2; k++) {
//abs(j - dx) + abs(k - dy) == 1 确保只判断九宫格的四个特定位置
if (abs(j - dx) + abs(k - dy) == 1 && map[j][k] == ROUTE) {
count++;
}
}
}
if (count > 1) {
break;
}
//确保不会挖穿时,前进
--range;
map[dx][dy] = ROUTE;
}
//没有挖穿危险,以此为节点递归
if (range <= 0) {
InitMaze(Rank2,dx,dy);
}
}
}
//--------------------------------------------------创建迷宫----------------------------------------------------------
void CreateMaze(int Rank1,int L,int* pendx,int* pendy)
{
int BAR = 0 ;//墙和路径的标识
int ROUTE = 1 ;//墙和路径的标识
srand((unsigned)time(NULL));//随机数种子
//最外围层设为路径的原因,为了防止挖路时挖出边界,同时为了保护迷宫主体外的一圈墙体被挖穿
for (int i = 0; i < L; i++){
map[i][0] = ROUTE;
map[0][i] = ROUTE;
map[i][L - 1] = ROUTE;
map[L - 1][i] = ROUTE;
}
//创造迷宫,(2,2)为起点
InitMaze(Rank1,2,2);
//画迷宫的入口和出口
map[2][1] = ROUTE;//迷宫的入口 也就是 游戏人物的起点
//由于算法随机性,出口有一定概率不在(L-3,L-2)处,此时需要寻找出口
for (int i = L - 3; i >= 0; i--) {
if (map[i][L - 3] == ROUTE) {
map[i][L - 2] = ROUTE;
*pendy = i;
*pendx = L-1;
break;
}
}
//1、0替换
for (int i = 1; i < L-1; i++) {
for (int j = 1; j < L-1; j++) {
if (map[i][j] == ROUTE) {
map[i][j] = PASS;
}
else {
map[i][j] = WALL;
}
}
}
}
//--------------------------------------------------------主函数--------------------------------------------------------------
int main()
{
//变量声明
int val;
int endx,endy,curx,cury;
//-------------------------------------------------------模式选择-------------------
//模式选择 : 0 随机生成迷宫 1 手动输入迷宫
int pattern;
cout << "请选择构建迷宫的方式:\n0.随机生成迷宫\n1.手动输入迷宫\n" << endl; //实际上不是0就可以手动输入
cin >> pattern ;
//-----------------------------------------------0 随机生成迷宫-------------------------------------
if(pattern == 0) {
int L;
cout << "请输入构建迷宫的大小:" << endl;
cin >> L;
m = L;
n = L;
int Rank;
cout << "请输入构建迷宫的复杂度Rank:(Rank越大迷宫越简单,Rank最小值为0)" << endl;
cin >> Rank;
CreateMaze(Rank,L,&endx,&endy); //对迷宫数据进行初始化 map[i][j]
//!!!!!!!设置迷宫的出口和入口
//endy = *pendy, endx = *pendx; // !!!出口列和行,列是x,y是行
curx = 1 , cury = 2; // !!!主角开始行和列
map[endy][endx] = EXITS;
}
// -------------------------------------------1 手动输入迷宫--------------------------------------------------
else{
cout << "请输入地图行数和列数:" << endl;
cin >> m >> n;
cout << "请输入地图数据:" << endl;
//根据输入初始化地图
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++){
cin >> val;
map[i][j] = val;
}
}
//!!!!!!!设置迷宫的出口和入口
endy = m - 2, endx = n - 1; // !!!出口列和行,列是x,y是行
curx = 1, cury = 1; // !!!主角开始行和列
map[endy][endx] = EXITS;// 给出口位置标记
}
initgraph(n * 80, m * 80,SHOWCONSOLE); // 创建绘图窗口,大小为 n * 80,m * 80 像素
LoadImags();
// 2.深度优先搜索,找到路径
DFS(cury, curx, DOWN, cury, curx, map[cury][curx]);
// 3.根据栈保存的信息,修改地图访问的路径
UpdateMapToPath();
DrawMap();
_getch(); // 按任意键继续
closegraph(); // 关闭绘图窗口
}
//----------------------------------参考数据--------------------------------------
/*
地图值1:
10 10
1 1 1 1 1 1 1 1 1 1
1 0 0 1 0 0 0 1 0 1
1 0 0 1 0 0 0 1 0 1
1 0 0 0 0 1 1 0 0 1
1 0 1 1 1 0 0 0 0 1
1 0 0 0 1 0 0 0 0 1
1 0 1 0 0 0 1 0 0 1
1 0 1 1 1 0 1 1 0 1
1 0 0 0 0 1 0 0 0 1
1 1 1 1 1 1 1 1 1 1
地图值2:
11 10
1 1 1 1 1 1 1 1 1 1
1 0 0 1 0 0 0 1 0 1
1 0 0 1 0 0 0 1 0 1
1 0 0 0 0 1 1 0 0 1
1 0 1 1 1 0 0 0 0 1
1 0 0 0 1 0 1 0 0 1
1 0 1 0 0 0 1 0 0 1
1 0 1 1 1 0 1 1 0 1
1 0 0 0 0 1 0 1 0 1
1 0 1 0 0 0 0 1 0 1
1 1 1 1 1 1 1 1 1 1
*/
代码直接复制粘贴运行是看不到图形界面的,需要在代码文件的同级文件夹里保存6张供加载的照片,需要的可以私信我,当然,也可以使用自己喜欢的图片
五、运行效果
1、静态迷宫
(1)测试数据:
-
1
-
10 10
-
1 1 1 1 1 1 1 1 1 1
1 0 0 1 0 0 0 1 0 1
1 0 0 1 0 0 0 1 0 1
1 0 0 0 0 1 1 0 0 1
1 0 1 1 1 0 0 0 0 1
1 0 0 0 1 0 0 0 0 1
1 0 1 0 0 0 1 0 0 1
1 0 1 1 1 0 1 1 0 1
1 0 0 0 0 1 0 0 0 1
1 1 1 1 1 1 1 1 1 1
(2)效果:
2、动态生成迷宫
(1)测试数据:
- 0
- 10
- 0
(2)效果:
如果对你有帮助的话就点个赞吧!有不太懂的地方可以留言或私信,看到的话都会及时回复的!