实现功能:
使用不相交集合数据(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等。