数据结构:迷宫问题
【迷宫问题】
- 生成全连通迷宫
生成NN大小的迷宫
随机连通该迷宫的各个节点,使该迷宫全连通
通路数不超过1.5N*N-N-0.5
迷宫节点用数字表示,位数等于最大编号的位数,位数不足的编号用0补齐
保证各行各列对齐
不同节点之间的通路用短横(竖)线表示,不存在通路的用空格表示 - 求最短路径
输入任意两点间的编号
输出这两点之间的所有最短路径
【核心算法】
Dijstra算法求最短路径
#include <iostream>
#include <iomanip>//I/O流控制头文件
#include <algorithm>//非自定义泛型算法头文件
#include <vector>
#include <set>
#include <ctime>//获取当前时间的库函数
using namespace std;
//定义节点结构体
struct Node {
int id, col, row;//节点编号,行数,列数
Node* left, * right, * up, * down;//每个节点都包含左右上下四个指针
set<Node*> connected;//每个节点有一个连接集,记录已经连通的节点,当所有节点的连通集都包含所有节点时,该迷宫全连通
//节点构造函数
Node(int id, int row, int col) {
this->id = id;
this->row = row;
this->col = col;
this->connected.insert(this);//将该节点放入自身的连接集中
this->left = nullptr;
this->right = nullptr;
this->up = nullptr;
this->down = nullptr;
}
//节点析构函数,用于释放内存空间(当结构体中包含非基本变量时需要使用析构函数)
~Node() {
set<Node*>().swap(connected);
left = nullptr;
right = nullptr;
up = nullptr;
down = nullptr;
}
};
typedef vector<Node*> Path;//路径
typedef set<Path> PathSet;//路径集合,这是一个三维向量
//比较节点id大小
bool compareID(const Node* n1, const Node* n2) {
return n1->id < n2->id;
}
int calculate(int i, int j, int N) {
int k;
k = (N * (N - 1) / 2) - (N - i) * ((N - i) - 1) / 2 + j - i - 1;
//cout << k << endl;
return k;
}
struct Maze {
int size;//迷宫大小
vector<Node*> nodes;//节点集合
int numEdges;//边数
int maxEdges;//最大边数
vector<PathSet> pathSets;//路径集合,三维向量
vector<int> shortest;//最短路径长度
//创建迷宫
Maze(int N) {
this->size = N;
this->numEdges = 0;
this->maxEdges = 2 * N * (N - 1);
int id = 0;
//利用循环创建新节点,并放入节点集合中
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
Node* n = new Node(id++, i + 1, j + 1);
nodes.push_back(n);
}
}
sort(nodes.begin(), nodes.end(), compareID);//给节点排序
//路径初始化
for (int i = 0; i < nodes.size(); ++i) {
for (int j = i + 1; j < nodes.size(); ++j) {
pathSets.push_back(PathSet());
shortest.push_back(INT_MAX);
}
}
}
Node* getNode(int row, int col) {
int id = (col - 1) + (row - 1) * size;
return nodes[id];
}
//加一条边的函数
set<Node*> addEdge(int edge) {
set<Node*> s;
int row, col;
Node* n1, * n2;
if (edge < size * (size - 1)) {
row = edge / (size - 1) + 1;//根据边的id值计算所在行
col = edge % (size - 1) + 1;//根据边的id值计算所在列
n1 = getNode(row, col);
n2 = getNode(row, col + 1);
n1->right = n2;
n2->left = n1;
}//横向加边
else {
row = (edge - size * (size - 1)) / size + 1;//根据边的id值计算所在行
col = (edge - size * (size - 1)) % size + 1;//根据边的id值计算所在列
n1 = getNode(row, col);
n2 = getNode(row + 1, col);
n1->down = n2;
n2->up = n1;
}//纵向加边
s = n1->connected;
s.insert(n2->connected.begin(), n2->connected.end());//更新当前节点连接集
//更新所有节点连接集
for (auto& i : s) {
i->connected = s;
}
numEdges++;//边数+1
return s;
}
//随机加边并判断该迷宫是否全连通
int Connected() {
bool flag = false;
bool all = false;
vector<int> availableEdges;//该集合用于存放尚未连通的边
//初始化状态,所有边都未连通
for (int edgeid = 0; edgeid < maxEdges; ++edgeid) {
availableEdges.push_back(edgeid);
}
while (!flag) {
srand((time(0)));//以当前时间作为随机数种子
int randid = rand() % availableEdges.size();//随机获取一条边
set<Node*> connected = addEdge(availableEdges[randid]);//将这条边连通
availableEdges.erase(availableEdges.begin() + randid);//将这条边从未连通的边的集合中删除
if (connected.size() == nodes.size()) all = true;//当连接集中的节点个数=节点总数时,该迷宫全连通
if (all && numEdges >= 1.5 * size * size - size - 0.5) flag = true;//当该迷宫全连通且边数到达指定数量时,退出循环,结束加边
}
return numEdges;//返回总边数
}
//打印迷宫函数
void PrintMaze() {
int idm = size * size - 1;
int max = 0;
int id;
//计算节点id值的最大位数max
while (idm)
{
max++;
idm = idm / 10;
}
//打印迷宫
for (int i = 0; i < size; ++i)
{
for (int j = 0; j < size; ++j)
{
int a = 0;//a用于记录每个节点id的位数
id = i * size + j;//当前节点id
//计算当前节点id值的位数a
while (id)
{
a++;
id = id / 10;
}
//输出当前节点,位数不足补零
if (a == 0)
{
for (int k = 0; k < (max - a - 1); ++k)
{
cout << "0";
}
}
else if (a < max)
{
for (int k = 0; k < (max - a); ++k)
{
cout << "0";
}
}
cout << i * size + j;
//横向输出
if (nodes[i * size + j]->right != nullptr && j != size - 1)
{
cout << "—";
}
else cout << " ";
}
cout << endl;
//纵向输出
for (int j = 0; j < size; ++j)
{
if (nodes[i * size + j]->down != nullptr && i != size - 1)
{
cout << "|";
for (int k = 0; k < max + 1; ++k)
cout << " ";
}
else
{
for (int k = 0; k < max + 2; ++k)
cout << " ";
}
}
cout << endl;
}
}
//寻找距离当前节点最近的节点
Node* FindMin(int start, vector<Node*>& V) {
int minid = 0;
int mindd = INT_MAX;
//遍历所有节点
for (int i = 0; i < V.size(); ++i) {
int dst = V[i]->id;//当前节点
int t;
if (dst == start) {
t = 0;//若当前节点也是起始节点,t=0
}
else {
//cout << "nodes.size is " << nodes.size() << endl;
t = shortest[calculate(start, dst, nodes.size())];//否则,t=当前节点到起始节点的最短路径长
}
if (t < mindd) {
mindd = t;
minid = i;
}//若t<mindd,则说明当前节点是目前已知的距离起始节点最近的节点,记录该路径长及当前节点的id值
}//遍历所有节点后,minid即为距离起始节点最近的节点的id值
Node* n = V[minid];
V.erase(V.begin() + minid);
return n;
}
//Dijkstra算法寻找两点间最短路径
void Dijkstra(int start) {
vector<Node*> S, V;
V = vector<Node*>(nodes.begin(), nodes.end());
while (V.size() > 0) {
Node* n = FindMin(start, V);//找到距离当前节点最近的节点
int D_n;
PathSet pathsettt;
if (n->id != start) {
D_n = shortest[calculate(start, n->id, nodes.size())];
pathsettt = pathSets[calculate(start, n->id, nodes.size())];
}
else {
D_n = 0;
Path p;
p.push_back(n);
pathsettt.insert(p);
}
S.push_back(n);
Node* L, * R, * U, * D;
L = n->left;
R = n->right;
U = n->up;
D = n->down;
int idx;
//若节点n的左边是连通的
if (L != nullptr && L->id > start) {
idx = calculate(start, L->id, nodes.size());
int D_l = shortest[calculate(start, L->id, nodes.size())];
//新路径比原路径更短或等长
if (D_n + 1 <= D_l) {
shortest[calculate(start, L->id, nodes.size())] = D_n + 1;
for (auto& p : pathsettt) {
Path pl = p;
pl.push_back(L);//将新路径存入路径集合
pathSets[calculate(start, L->id, nodes.size())].insert(pl);//更新路径集合
}
}
}
//若节点n的右边是连通的
if (R != nullptr && R->id > start) {
int D_r = shortest[calculate(start, R->id, nodes.size())];
//新路径比原路径更短或等长
if (D_n + 1 <= D_r) {
shortest[calculate(start, R->id, nodes.size())] = D_n + 1;
for (auto& p : pathsettt) {
Path pr = p;
pr.push_back(R);//将新路径存入路径集合
pathSets[calculate(start, R->id, nodes.size())].insert(pr);//更新路径集合
}
}
}
//若节点n的上边是连通的
if (U != nullptr && U->id > start) {
int D_u = shortest[calculate(start, U->id, nodes.size())];
//新路径比原路径更短或等长
if (D_n + 1 <= D_u) {
shortest[calculate(start, U->id, nodes.size())] = D_n + 1;
for (auto& p : pathsettt) {
Path pu = p;
pu.push_back(U);//将新路径存入路径集合
pathSets[calculate(start, U->id, nodes.size())].insert(pu);//更新路径集合
}
}
}
//若节点n的下边是连通的
if (D != nullptr && D->id > start) {
int D_d = shortest[calculate(start, D->id, nodes.size())];
//新路径比原路径更短或等长
if (D_n + 1 <= D_d) {
shortest[calculate(start, D->id, nodes.size())] = D_n + 1;
for (auto& p : pathsettt) {
Path pd = p;
pd.push_back(D);//将新路径存入路径集合
pathSets[calculate(start, D->id, nodes.size())].insert(pd);//更新路径集合
}
}
}
}
}
};
//打印所有最短路径
void printPath(Path path) {
for (int i = 0; i < path.size() - 1; ++i) {
cout << path[i]->id << " -> ";
}
cout << path[path.size() - 1]->id << endl;
}
int main()
{
int n;
cin >> n;
cout << endl;
Maze maze = Maze(n);
maze.Connected();
cout << "总边数: " << maze.numEdges << endl;
cout << endl;
maze.PrintMaze();
//对所有节点运用Dijkstra算法找出任意两点间的最短路径
for (int i = 0; i < n * n - 1; ++i) {
maze.Dijkstra(i);
}
int id1, id2;
cout << "请输入起始节点和终止节点的编号(要求:起始节点小于终止节点)" << endl;
cout << endl;
cin >> id1 >> id2;
cout << endl;
int length = maze.shortest[calculate(id1, id2, maze.nodes.size())];
cout << "从 " << id1 << " 到 " << id2 << " 的最短路径长为: " << length << endl;
cout << endl;
PathSet ps = maze.pathSets[calculate(id1, id2, maze.nodes.size())];
cout<< "以下为从 " << id1 << " 到 " << id2 << " 的所有最短路径: " << endl;
cout << endl;
for (auto& p : ps) {
printPath(p);
cout << endl;
}
}
【运行结果】
随机生成7*7的全连通迷宫