C++用不相交集合构建随机迷宫并搜寻最短路径

实现功能:

使用不相交集合数据(disjointsetdatastructure) 来构造一个N乘N的从左上角到右下角只有一条路径的随机迷宫,然后在这一迷宫上执行深度优先搜索。

思想描述

该设计共包含如下四个部分:
①不相交集合数据结构的设计和实现

不相交集合即对于任意两个集合 A 和 B,A∩B=ø。不相交集合常可以表示 为树,此时两个不相交集合的并的实现很容易,如图所示。不相交集合常可用来 根据等价关系对集合进行等价划分。
在这里插入图片描述

②构建随机迷宫 应用不相交集合构建迷宫的算法简要描述如下:给定一个 N X N 的方格(cells),初始时每个方格的四面都是墙(walls),如图 6-58(a)所示,其中的 S 是迷宫的开始处,F 是迷宫的结束处。NN 迷宫的 N2 个方格 0,1,…,N2-1 初始时每个方格自己成为一个等价类,即{0},{1},…,{N2-1}。生成随机迷宫 的方法是随机选择一个内部墙(连接两个相邻方格的墙),如果该内部墙关联的 两个相邻的方格属于不同的等价类就将该墙除去,在除去该墙的同时将这两个等 价类合并。直到所有的方格都在一个等价类中,就完成了随机迷宫的生成。
在这里插入图片描述

③寻找迷宫路径 迷宫一旦建立后,将迷宫表示为一个无向图:方格作为图中的顶点,如果两
个相邻的方格之间没有墙则两个顶点之间有边。为找到从 S 到 F 的一条唯一的 路径,在该图上从 S 处开始出发先深搜索,如果搜索到达了 F,则搜索停止。
④将迷宫和路径用图形方式画出 用图形方式将上述算法获得的随机迷宫及其上的最短路径画出。用线段来表
示迷宫中的墙,用在每个方格中心的点来表示路径。

设计

Maze的设计共包含如下四个部分:
1 不相交集合的设计与实现(Box&Maze类)
2 构建随机迷宫(Create_Maze( ))
3 寻找迷宫路径(Search_Path( ))
4 将迷宫路径用图形方式画出(Display_Maze( ))。
其中Box用于模拟迷宫方格,完成对不相交集合的模拟;Maze::Create_()用于随机化选择方向“拆墙”,从而构建随机迷宫;在构建好随机迷宫的基础上利用Maze::Search_Path( )寻找最佳走出迷宫路径,Maze::Display_Maze()将迷宫及迷宫路径以图形的方式画出
其中 Find_Ancestor( ) 用于寻找方格所在的集合及祖先所在的位置
Is_Connect( ) 查看两个房格是否连通
MergeBox( ) 合并统一祖先的方格
Path_In( ) 查看小方格是否在路径中

针对这一迷宫系统,需要管理的数据主要有:Box方格;Box方格形成的迷宫结构;现就每种数据给出详细的分析。

图1 构建随即迷宫及找寻路径的基本过程

(一)Box之间的关系是对等的,形成一种顺序结构。
所以此处需构建线性表结构。
由于我们不考虑Box的增加和较少,所以在这一线性表上不需要定义增加和删除的操作,只需要定义查询(定位)的操作,这是因为需要定位某个Box
其ADT可定义为:
ADT Box
{
数据之间的逻辑结构为线性结构;
基本操作:
Box Locate(int i); //定位第i个Sensor
}
(二)Box集上形成的迷宫出路Maze_Path结构是一种栈结构。,需在这一栈结构上定义如下基本操作:进栈,出栈。
其ADT定义为:
ADT Maze_Path
{
数据之间的逻辑结构为栈结构;
基本操作:
Maze_push( ); //进栈
Maze_pop(); //出栈
}
1.迷宫初始化建立的算法的设计
迷宫初始化建立最主要的就是并查集的实现
其基本思想就是集合用树结构(父链)来表示,令集合的元素对应数组的下标,而相应的元素值表示其父结点所对应的数组单元下标。其基本操作包括“并”:把其中一株当成另一株的子树。“包含”:求元素所在的树根。其“并”的核心算法如下:改进并操作的规则,将结点少的并入结点多的;相应存储结构也要提供支持——以加权规则压缩高度。
2.最佳路径搜索
最佳路径的搜索算法的实现
在这里插入图片描述

3.迷宫信息图形化
在该系统中,主要完成如下三个部分的可视化:
•建立迷宫之前的方格集合。
•“破墙”之后建立的随机迷宫
•搜索最佳路径之后,将路径显示在迷宫之中。

完整源代码

Main_Program.cpp

#include"iostream"
#include"Maze.h"
using namespace std;

int main()
{
	cout << "请输入迷宫的列数和行数:" << endl;
	int m, n; cin >> m >> n;
	Maze M(m,n);
	M.CreateMaze();
	M.DisPlay();
	vector<Box> BoxTeam;
	M.Search_Path(BoxTeam);
	M.DisPlay(BoxTeam);
	//M.Display("D:\\文档\\迷宫.txt");//这两个是输出到文件,如果不需要就不用
	//M.DisPlay("D:\\文档\\迷宫——带路径.txt", BoxTeam);
	
	system("pause");
	return 0;
}

Maze.h

#pragma once
#include<vector>
using namespace std;

struct Box
{
	int Ancestor;//记录方格(树)的祖先
	int position;//记录方格的位置
	int Status[4];//记录四面墙的状态,开为1,闭为0
};

class Maze
{
public:
	Maze(int m, int n);//重载构造函数
	void CreateMaze();//构建迷宫
	void Search_Path(vector<Box> &BoxTeam);//寻找迷宫路径
 	void Display(const string & str);//文件输出迷宫
	void DisPlay(const string &str, vector<Box>& v);//文件输出带路径的迷宫
	void DisPlay();//控制台输出迷宫
	void DisPlay(vector<Box>& v);//控制台输出带路径的迷宫

	~Maze();
private:
	vector<Box> MyMaze;//存储迷宫
	int row;//每行房间数
	bool Path_In(vector<Box>& BoxTeam, Box &m);//查询迷宫中房间是否在迷宫路径中
	bool Is_Connect(Box &m, Box &n);//查看两个房间是否相连通
	int Find_Ancestor(Box &m,int &n);//查找集合并保留祖宗的位置
	void MergeBox(Box &m, Box &n);//合并房间以生成路径
};


Maze.cpp

#include "Maze.h"
#include"time.h"
#include"fstream"
#include"string"
#include"iostream"
using namespace std;

Maze::Maze(int m , int n) :row(m), MyMaze(m*n)
{
	int s = MyMaze.size();//迷宫中方格的数量
	if (s == 0)
		return;
	for (int i = 0; i < s; i++)
	{
		MyMaze[i].Ancestor = -1;//所有祖先都置为-1
		MyMaze[i].position = i;//位置保持对应
		for (int b = 0; b < 4; b++)
			MyMaze[i].Status[b] = 0;//0为闭

		MyMaze[0].Status[2] = 1;//设置迷宫入口和出口,左上进,右下出
		MyMaze[s - 1].Status[0] = 1;
	}
}

//创建迷宫
void Maze::CreateMaze()
{
	int s = MyMaze.size();
	if (s <= 0)  return;
	int p, w;
	int pos;

	srand(time(0));//随机种子
	while (1)
	{

		p = rand() % s;
		w = rand() % 4;

		switch (w)
		{
		case 0://右端
			if ((p%row) == (row - 1))//到了这一行的最右端,此时不需要开口
				break;
			if (Is_Connect(MyMaze[p], MyMaze[p + 1]))//右端已经相连,不需要开口
				break;
			MergeBox(MyMaze[p], MyMaze[p + 1]);//合并为一个集合(树)
			MyMaze[p].Status[0] = 1;
			MyMaze[p + 1].Status[2] = 1;
			break;

		case 1://下端
			if ((s - p) <= row)//已经在最下面一行了,无需开口
				break;
			if (Is_Connect(MyMaze[p], MyMaze[p + row]))//与下面已经相连
				break;
			MergeBox(MyMaze[p], MyMaze[p + row]);
			MyMaze[p].Status[1] = 1;
			MyMaze[p + row].Status[3] = 1;
			break;

		case 2://左端
			if ((p%row) == 0)//已经在一行的最左边了
				break;
			if (Is_Connect(MyMaze[p], MyMaze[p - 1]))//已经与左边相连
				break;
			MergeBox(MyMaze[p], MyMaze[p - 1]);
			MyMaze[p].Status[2] = 1;
			MyMaze[p - 1].Status[0] = 1;
			break;

		case 3://上端
			if (p < row)//已经在最上面的一行了
				break;
			if (Is_Connect(MyMaze[p], MyMaze[p - row]))
				break;
			MergeBox(MyMaze[p], MyMaze[p - row]);
			MyMaze[p].Status[3] = 1;
			MyMaze[p - row].Status[1] = 1;
			break;

		}

		if (Find_Ancestor(MyMaze[0], pos) == (-1 * s))//所有的方格都被
			break;
	}
};

//深度优先搜索寻找最短路径
void Maze::Search_Path(vector<Box>& BoxTeam)
{
	int s = MyMaze.size();
	if (BoxTeam.size() == 0)
		BoxTeam.push_back(MyMaze[0]);//vector的push_back操作是将一个元素插入vector的末尾,也就是把入口压入栈
	int i = 0;
	while (i < 4)//对四个门进行操作
	{
		switch (i)
		{
		case 0:
			if (BoxTeam.back().position == (s - 1))//已经到了最后一个,则退出
				break;
			if (BoxTeam.back().Status[i] == 1)
			{
				BoxTeam.push_back(MyMaze[BoxTeam.back().position + 1]);//若i=0,右边一个入栈BoxTeam
				BoxTeam.back().Status[2] = 0;
				Search_Path(BoxTeam);
			}
			break;

		case 1:
			if (BoxTeam.back().position == (s - 1))
				break;
			if (BoxTeam.back().Status[i] == 1)
			{
				BoxTeam.push_back(MyMaze[BoxTeam.back().position + row]);//若i=1,下面一个入栈BoxTeam
				BoxTeam.back().Status[3] = 0;
				Search_Path(BoxTeam);
			}
			break;

		case 2:
			if (BoxTeam.back().position == (s - 1))
				break;
			if (BoxTeam.back().Status[i] == 1)
			{
				BoxTeam.push_back(MyMaze[BoxTeam.back().position - 1]);//若i=2,左边一个入栈BoxTeam
				BoxTeam.back().Status[0] = 0;
				Search_Path(BoxTeam);
			}
			break;

		case 3:
			if (BoxTeam.back().position == (s - 1))
				break;
			if (BoxTeam.back().Status[i] == 1)
			{
				BoxTeam.push_back(MyMaze[BoxTeam.back().position - row]);//若i=3,上面一个入栈BoxTeam
				BoxTeam.back().Status[1] = 0;
				Search_Path(BoxTeam);
			}
			break;
		}
		if (BoxTeam.back().position == (s - 1))//若刚刚好结束,则退出
			break;
		i++;//计数
	}
	if (i == 4)
		BoxTeam.pop_back();//删除vector中最后一个元素,也就是在四面不通的时候出栈
};



void Maze::DisPlay(const string &str, vector<Box>& v)
{
	vector<Box>::iterator iter = MyMaze.begin();
	int s = MyMaze.size();
	int count = 0;
	ofstream outfile;
	outfile.open(str.c_str(), ofstream::app);
	if (!outfile)
	{
		cout << "Can't open the file" << str << endl;
		return;
	}

	count = 0;
	iter = MyMaze.begin();

	outfile << "Maze(with path)" << endl;
	while (iter != (MyMaze.begin() + row))
	{
		if (iter == MyMaze.begin())
		{
			outfile << " _ ";
			++iter;
			continue;
		}

		outfile << "_ ";
		++iter;
	}
	outfile << endl << ' ';

	iter = MyMaze.begin();
	while (iter != MyMaze.end())
	{
		if (iter->Status[1] == 0)
			outfile << "_";
		else
		{
			if ((MyMaze.end() - iter) <= row)
				outfile << '_';
			else
			{
				if (Path_In(v, *iter))
					outfile << '*';
				else
					outfile << ' ';
			}
		}

		if (iter->Status[0] == 0)
			outfile << '|';
		else
		{
			if (Path_In(v, *iter) )
				outfile << '*';
			else
				outfile << ' ';
		}

		++count;
		if (count%row == 0)
		{
			if (count >= s)
				outfile << endl << endl;
			else
				outfile << endl << "|";
		}
		++iter;
	}
	outfile << endl;
	outfile.close();
}

void Maze::Display(const string &str)
{
	vector<Box>::iterator iter = MyMaze.begin();
	int s = MyMaze.size();
	int count = 0;

	ofstream outfile;
	outfile.open(str.c_str(), ofstream::app);
	if (!outfile)
	{
		cout << "ERROR!Con not Input the Maze MSG into the File:" << str << endl;
		return;
	}
	count = 0;
	iter = MyMaze.begin();

	outfile << "Maze" << endl;
	while (iter != (MyMaze.begin() + row))
	{
		if (iter == MyMaze.begin())
		{
			outfile << " _ ";
			++iter;
			continue;
		}

		outfile << "_ ";
		++iter;
	}
	outfile << endl << ' ';

	iter = MyMaze.begin();
	while (iter != MyMaze.end())
	{
		if (iter->Status[1] == 0)
			outfile << '_';
		else
		{
			if ((MyMaze.end() - iter) <= row)
				outfile << '_';
			else
				outfile << ' ';
		}

		if (iter->Status[0] == 0)
			outfile << '|';
		else
			outfile << ' ';

		++count;
		if (count%row == 0)
		{
			if (count >= s)
				outfile << endl << endl;
			else
				outfile << endl << '|';
		}
		++iter;
	}

	outfile << endl;
	outfile.close();

};

void Maze::DisPlay()
{
	vector<Box>::iterator iter = MyMaze.begin();
	int s = MyMaze.size();
	int count = 0;//计数器

	iter = MyMaze.begin();

	cout << "Maze" << endl;
	while (iter != (MyMaze.begin() + row))//对于 第一行
	{
		if (iter == MyMaze.begin())
		{
			cout << " _ ";//对于入口的输出,在前面空一格
			++iter;
			continue;
		}

		cout << "_ ";//第一行的围墙
		++iter;
	}
	cout << endl << ' ';//换行并空一格

	iter = MyMaze.begin();
	while (iter != MyMaze.end())
	{
		if (iter->Status[1] == 0)//如果方格下面有墙,就输出
			cout << '_';
		else
		{
			if ((MyMaze.end() - iter) <= row)//如果已经到了最后一行,则输出下面的围墙
				cout << '_';
			else
				cout << ' ';//没有墙就输出空格
		}

		if (iter->Status[0] == 0)//右边有墙
			cout << '|';
		else
			cout << ' ';

		++count;//下方和右方都找了一次,此时计数器加一;
		//输出的时候并没有按照上下左右分别输出四个方向,而是分别输出边界,然后只考虑两个方向
		if (count%row == 0)
		{
			if (count >= s)
				cout << endl << endl;//如果已经输出完毕,就空两格
			else
				cout << endl << '|';//输出迷宫左右两列的墙壁
		}
		++iter;//迭代器每次都要加一
	}

	cout << endl;
}
void Maze::DisPlay(vector<Box>& v)
{
	vector<Box>::iterator iter = MyMaze.begin();
	int s = MyMaze.size();
	int count = 0;
	
	iter = MyMaze.begin();

	cout << "Maze(with path)" << endl;
	while (iter != (MyMaze.begin() + row))
	{
		if (iter == MyMaze.begin())
		{
			cout << " _ ";
			++iter;
			continue;
		}

		cout << "_ ";
		++iter;
	}
	cout << endl << ' ';

	iter = MyMaze.begin();
	while (iter != MyMaze.end())
	{
		if (iter->Status[1] == 0)
			cout << "_";
		else
		{
			if ((MyMaze.end() - iter) <= row)
				cout << '_';
			else
			{
				if (Path_In(v, *iter))
					cout << '*';
				else
					cout << ' ';
			}
		}

		if (iter->Status[0] == 0)
			cout << '|';
		else
		{
			if (Path_In(v, *iter) )
			{
				cout << '*';
			}
			else
				cout << ' ';
		}

		++count;
		if (count%row == 0)
		{
			if (count >= s)
				cout << endl << endl;
			else
				cout << endl << "|";
		}
		++iter;
	}
	cout << endl;
}
;

Maze::~Maze()
{
}
//查询迷宫中房间是否在迷宫路径中
bool Maze::Path_In(vector<Box>& BoxTeam, Box& m)
{
	vector<Box>::iterator iter = BoxTeam.begin();//使用容器的迭代器来遍历整个容器
	while (iter != BoxTeam.end())
	{
		if (iter->position == m.position)
			return true;
		iter++;
	}
	return false;
};


bool Maze::Is_Connect(Box & m, Box & n)//查看两个房间时候相连通
{
	int p, q;
	if (Find_Ancestor(m, p) == Find_Ancestor(n, q))//判断 祖先数(深度) 是否相同
	{
		if (Find_Ancestor(m, p) == -1) //-1代表两房间不在任何集合中
			return false;
		else
			if (p == q)//判断 祖先位置 是否相同
				return true;
			else return false;
	}
	return false;
}
//寻找方格所在的集合以及祖先所在的位置
int Maze::Find_Ancestor(Box& m, int& n)
{
	int s = m.Ancestor;
	n = m.position;
	if (s >= 0)
		s = Find_Ancestor(MyMaze[s], n);
	return s;
};
//合并统一祖先的方格
void Maze::MergeBox(Box& m, Box& n)
{
	int s1, s2, p1, p2;

	s1 = Find_Ancestor(m, p1);
	s2 = Find_Ancestor(n, p2);

	if (s1 <= s2)//因为是以负数表示深度,这里是s1比s2深(m比n深),因此s2的祖先指向s1
	{
		MyMaze[p1].Ancestor += MyMaze[p2].Ancestor;//先把祖先的深度合并到m上
		MyMaze[p2].Ancestor = m.position;//s2(n)的祖先指向s1(m)
	}
	else
	{
		MyMaze[p2].Ancestor += MyMaze[p1].Ancestor;
		MyMaze[p1].Ancestor = n.position;
	}

};

运行实例:
对90*45
在这里插入图片描述

在这里插入图片描述
对25*40
在这里插入图片描述
在这里插入图片描述

注意
以上是在控制台上的输出结果,若是在文件内输出,则无法保证对齐,实例如下:
在这里插入图片描述

在这里插入图片描述

建议:
有兴趣的同学可以采用图形化输出,使用Qt或者MFC等。

  • 9
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值