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等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章