實現功能:
使用不相交集合數據(disjointsetdatastructure) 來構造一個N乘N的從左上角到右下角只有一條路徑的隨機迷宮,然後在這一迷宮上執行深度優先搜索。
思想描述
該設計共包含如下四個部分:
①不相交集合數據結構的設計和實現
不相交集合即對於任意兩個集合 A 和 B,A∩B=ø。不相交集合常可以表示 爲樹,此時兩個不相交集合的並的實現很容易,如圖所示。不相交集合常可用來 根據等價關係對集合進行等價劃分。
②構建隨機迷宮 應用不相交集合構建迷宮的算法簡要描述如下:給定一個 N X N 的方格(cells),初始時每個方格的四面都是牆(walls),如圖 6-58(a)所示,其中的 S 是迷宮的開始處,F 是迷宮的結束處。NN 迷宮的 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等。