【C++】20191206 迷宫问题

数据结构:迷宫问题

【迷宫问题】

  1. 生成全连通迷宫
    生成NN大小的迷宫
    随机连通该迷宫的各个节点,使该迷宫全连通
    通路数不超过1.5
    N*N-N-0.5
    迷宫节点用数字表示,位数等于最大编号的位数,位数不足的编号用0补齐
    保证各行各列对齐
    不同节点之间的通路用短横(竖)线表示,不存在通路的用空格表示
  2. 求最短路径
    输入任意两点间的编号
    输出这两点之间的所有最短路径

【核心算法】
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的全连通迷宫

在这里插入图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值