《数据结构与算法》《经典算法的设计与实现》成都信息工程大学dfs深度优先迷宫求解静态迷宫动态生成迷宫图形界面可视化c++

本文作者:涂欢
开发环境及语言:Dev-C++ 、 EasyX 、 C++
关键词:栈、深度优先、迷宫、可视化、静态迷宫、动态迷宫、复杂度、递归


前言

学习完《数据结构与算法》后,学校安排了一门专业选修课《经典算法的设计与实现》,我的选题是“迷宫求解”,因为我自己的专业是数字媒体技术,偏游戏方向,同时也希望在做项目的时候会比较有趣,所以选了迷宫游戏。以下是我从项目结题报告中截取的一些内容。完整的报告我也上传了压缩包,里面包含从开题、设计、到结题的所有文件,需要的可以自行下载,如果是本校的选了这个课题那恭喜你发现宝藏咯!


一、问题描述

主要利用栈实现,要求静态迷宫能准确找到路径,且能动态生成迷宫,显示所有路径。用图形界面显示所找到的路径。

二、开发环境及工具

三、项目设计

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)效果:

动态生成迷宫


如果对你有帮助的话就点个赞吧!有不太懂的地方可以留言或私信,看到的话都会及时回复的!

猜你喜欢

转载自blog.csdn.net/weixin_64072619/article/details/128536292