离散实验3,中国邮递员问题,代码留档

本文讲述了如何使用深度优先搜索和Dijkstra算法解决邮递员问题,包括判断图是否为欧拉图,处理奇数顶点,构建最小权重的欧拉路径,以及通过最大匹配找到最短路径。

解决最大匹配问题简单,暴力的做法就是深搜,每一条边都搜一下,看看能不能搜,能搜的话就可以搜可以不搜,不能搜索就标定这边不能搜索然后接着往下走,然后找其中的最大的匹配即可。

  而邮递员问题,这里用的是无向简单图,所以简单得多,检查一下是不是欧拉图,若是欧拉图,欧拉图由Hierholzer 算法来求,即欧拉图是由若干个圈构成的,那么最小的子问题就是把一个点的所有的圈都走一遍,走的时候删掉此边,那么一定能将整个欧拉图走完,然后倒序存入路径,这条欧拉路径就出来了,

  若不是欧拉图就有说法了,由握手定理可以知道,存在偶数个奇数顶点,若是要回到原点,只能通过加重边的方式使得这个图变为欧拉图,不然不存在欧拉环游使得从起始点回到起始点,将其变成欧拉图之后用回欧拉图的方法即可,问题是怎么求最短的路径,当前的权是1(不影响),若要变为欧拉图,那么奇度顶点之间一定是有带权路径的,这样才能使得这俩点度数为偶数,若奇度顶点有k个,最少有k/2个路径,由于只需要求奇度顶点之间的路径并且使得其最小,我们可以把奇度顶点单独拿出来,若有路径的则有边,由于给出的图是连通的,那么一定有边,这样就会形成一个k阶完全图,而且为了求加的权最小,这些完全图中的边的值为最小带权路径的值,比如点1到点3有两条路径,那么一定要选最短的那条作为边,这样能确保之后走的权最小。

  求出完全图之后,由于需要带权最小,那么任务就变成了求此完全图的最大匹配的最小带权组合,即存在若干个最大匹配,找权最小的那个,到这已经差不多了,然后将对应的路径加回原来的图中即可,这样得到的一定是欧拉图

参考代码如下:

#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;

//寻找所有的匹配
void dfs(int u,int n, int m, int grid[][1000], bool edgest[], bool pointst[], vector<vector<int>>& ans, vector<int>& setsize)
{
	if (u>m)
	{
		vector<int>path;//将搜索到的边存进去
		for (int i = 0; i < m; i++)
		{
			if (edgest[i])path.push_back(i);
		}
		setsize.push_back(path.size());
		ans.push_back(path);
		return;//深度搜索完毕,搜完边就行
	}
	int temp = 0;//临时用来计算此边关联的点有没有被搜索过
	int edge[2] = { 0 };//用来看是哪两个点
	for (int i = 0; i < n; i++)
	{
		if (grid[i][u]&&!pointst[i])
		{
			temp++;
			if (temp == 1)edge[0] = i;
			else edge[1] = i;
		}
	}
	if (temp == 2)//说明这条边能用
	{
		edgest[u] = true;
		pointst[edge[0]] = true;
		pointst[edge[1]] = true;
		dfs(u + 1, n, m, grid, edgest, pointst, ans, setsize);
		edgest[u] = false;
		pointst[edge[0]] = false;
		pointst[edge[1]] = false;
		dfs(u + 1, n, m, grid, edgest, pointst,ans, setsize);
	}
	else
	{   //这边不能用也能搜,但没有上面那么多,因为不用改变状态
		dfs(u + 1, n, m, grid, edgest, pointst, ans, setsize);
	}
}


void pathdfs(int x,int graph[][100],int n,vector<int>&ans) 
{
	for (int y = 0; y < n; ++y) 
	{
		if (graph[x][y] > 0) 
		{
			graph[x][y]--;
			graph[y][x]--;
			pathdfs(y,graph,n,ans);
		}
	}
	ans.push_back(x);
}



// 构造最短路径
void ConstructPath(const vector<int>& precursor, int start, int end, vector<int>& path) 
{
	path.clear();
	int curr = end;
	while (curr != start) 
	{ // 从终点回溯到起点
		path.push_back(curr);
		curr = precursor[curr];
	}
	path.push_back(start);

	reverse(path.begin(), path.end()); // 反转路径,使其从起点到终点
}

// Dijkstra算法实现
void Dijkstra(int start, vector<int>& distance, vector<int>& precursor, vector<vector<int>> adjacencyMatrix) 
{
	// 定义无穷大的距离值
	const int INF = INT_MAX;
	int n = adjacencyMatrix.size();
	distance.resize(n, INF); // 初始化距离数组为无穷大
	distance[start] = 0; // 起点到自身的距离为0
	precursor.resize(n, -1); // 初始化前驱节点数组,表示还未访问的节点前驱为-1

	priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq; // 小顶堆存储未访问节点
	pq.push(make_pair(0, start)); // 将起点加入小顶堆

	while (!pq.empty()) {
		int dist = pq.top().first;
		int currNode = pq.top().second;
		pq.pop();

		// 如果当前节点已经被访问过,跳过
		if (dist > distance[currNode]) {
			continue;
		}

		// 遍历当前节点的相邻节点
		for (int i = 0; i < n; ++i) 
		{
			if (adjacencyMatrix[currNode][i] != INF) 
			{ // 判断当前节点与相邻节点是否存在连接
				int newDistance = dist + adjacencyMatrix[currNode][i];
				if (newDistance < distance[i]) 
				{ // 如果新的距离更小
					distance[i] = newDistance;
					precursor[i] = currNode; // 更新前驱节点
					pq.push(make_pair(newDistance, i));
				}
			}
		}
	}
}

void postman(int n,int m,int grid[][1000])
{
	//分三种情况,欧拉图,半欧拉图,非欧拉图
	bool flag1 = false, flag2 = false, flag3 = false;//与上面情况对应
	vector<int> path;//存放每个点度数
	for (int i = 0; i < n; i++)
	{
		int sum = 0;
		for (int j = 0; j < m; j++)
		{
			if (grid[i][j])
				sum++;
		}
		path.push_back(sum);
	}
	int odd=0;//记录有多少个奇数顶点,同时存下奇数度顶点
	vector<int> oddnum;
	for (int i = 0; i < n; i++)
		if (path[i] % 2 == 1)
		{
			odd++;
			oddnum.push_back(i);
		}
	vector<int>result;//存放结果
	//若odd为0则代表其为欧拉图,所有的度数均为偶数
	//将关联矩阵转化为邻接矩阵和边矩阵
	int graph[50][100] = { 0 }, ed[50][100] = { 0 };
	for (int j = 0; j < m; j++)
	{
		vector<int>arr;
		for (int i = 0; i < n; i++)
		{
			if (grid[i][j])arr.push_back(i);
		}
		int L = arr[0], r = arr[1];
		graph[L][r] = 1;
		graph[r][L] = 1;
		ed[L][r] = j;
		ed[r][L] = j;
	}

	if (odd != 0)
	{
		int dfsgridst[100] = { 0 };
		for (int i=0; i < path.size(); i++)dfsgridst[path[i]] = i;
		int oddgrid[100][100] = {0};//存奇数顶点的对应的最短路的矩阵
		vector<vector<vector<int>>>oddpath(odd, vector<vector<int>>(odd));
		// 定义无穷大的距离值
		const int INF = INT_MAX;
		vector<vector<int>> adjacencyMatrix(n, vector<int>(n, INF));
		vector<int> ans;
		//用adjacencyMatrix存邻接矩阵
		for (int j = 0; j < m; j++)
		{
			vector<int>arr;
			for (int i = 0; i < n; i++)
			{
				//若不能直接到达那么距离是无穷
				if (grid[i][j])arr.push_back(i);
			}
			int L = arr[0], r = arr[1];
			adjacencyMatrix[L][r] = 1;
			adjacencyMatrix[r][L] = 1;
		}
		for (int i = 0; i < n; i++)adjacencyMatrix[i][i] = 0;

		for (int i = 0; i < odd; i++)
		{
			int start = oddnum[i]; // 起点
			vector<int> distance; // 保存最短距离的数组
			vector<int> precursor; // 保存前驱节点的数组
			
			Dijkstra(start, distance, precursor, adjacencyMatrix);
			for (int k = 0; k < distance.size(); k++)//distance里面存起点到第k个点的最短距离
			{
				for (int z = 0; z < odd; z++)
				{
					if (oddnum[z] == k)
					{
						oddgrid[i][z] = distance[k];
					}
				}
			}
			for (int j = 0; j < odd; j++)
			{
				if (i == j)
				{
					oddpath[i][j].push_back(0);
					continue;
				}
				vector<int> path; // 保存最短路径的数组
				int end = oddnum[j]; // 终点
				ConstructPath(precursor, start, end, path);	
				for (auto x : path)//存好路径
				{
					oddpath[i][j].push_back(x);
				}
			}		
		}

		vector<vector<int>>ansdfs;
		vector<int>setsize;
		bool edgest[100] = { false }, pointst[100] = { false };
		//将oddnum转化为关联矩阵,有n*(n-1)/2条边
		int dfsgrid[20][1000] = { 0 };
		int eddfs[100][100] = {0};
		for (int i = 0; i < odd; i++)
			for (int j = 0; j < odd; j++)eddfs[i][j] = -1;
		int idx = 0;
		for (int p = 0; p < odd; p++)
		{
			for (int z = p+1; z < odd; z++)
			{
				if (oddgrid[p][z])//若有边就将其存入关联矩阵当中
				{
					dfsgrid[p][idx] = 1;
					dfsgrid[z][idx] = 1;
					eddfs[p][z] = idx;
					eddfs[z][p] = idx;
					idx++;
				}
			}
		}
		dfs(0, odd, idx, dfsgrid, edgest, pointst, ansdfs, setsize);
		int len = ansdfs.size();
		int max = 0;//记录最大匹配的位置;
		for (int i = 0; i < len; i++)
		{
			if (setsize[i] > max)
				max = i;
		}
		int reslong = ansdfs[max].size();//记录找到的数组的长度
		//将所有的最大匹配集输出
		int mindfs = INF;
		int flagdfs = 0;
		for (int k = 0; k < len; k++)
		{
			if (setsize[k] == reslong)//若是最大匹配集的长度就将对应的路径加到矩阵当中
			{	
				int sum = 0;
				for (int i = 0; i < reslong; i++)
				{
					for (int edi = 0; edi < odd; edi++)
					{
						for (int edj = edi + 1; edj < odd; edj++)
						{
							if (ansdfs[k][i] == eddfs[edi][edj])
							{
								sum += oddpath[edi][edj].size()-1;
							}
						}
					}
				}
				if (sum < mindfs)
				{
					mindfs = sum;
					flagdfs = k;
				}
			}
		}
		vector<vector<int>>finalans;
		for (int i = 0; i < reslong; i++)
		{
			for (int edi = 0; edi < odd; edi++)
			{
				for (int edj = edi + 1; edj < odd; edj++)
				{
					if (ansdfs[flagdfs][i] == eddfs[edi][edj])
					{
						finalans.push_back(oddpath[edi][edj]);
					}
				}
			}
		}
		for (int i = 0; i < finalans.size(); i++)
		{
			for (int j = 0; j < finalans[i].size()-1; j++)
			{
				int Le = finalans[i][j], ri = finalans[i][j + 1];
				graph[Le][ri] += 1;
				graph[ri][Le] += 1;
			}
		}
	}
		vector<int> ans;
		pathdfs(0, graph, n,ans);
		int needlen = ans.size();
		vector<int> turned;
		printf("\n路径为{");
		for (int i = 0; i < needlen-1; i++)
		{
			printf("e%d", ed[ans[i]][ans[i + 1]]+1);
		}
		printf("}");

}

int main()
{
	int grid[100][1000] = { 0 };
	int n, m;

	vector<vector<int>>ans;
	vector<int>setsize;
	bool edgest[100] = { false }, pointst[100] = { false };
	cin >> n >> m;
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
			cin >> grid[i][j];
	}
	dfs(0, n, m, grid, edgest, pointst, ans, setsize);
	int len = ans.size();

	int max = 0;//记录最大匹配的位置;
	for (int i = 0; i < len; i++)
	{
		if (setsize[i] > max)
			max = i;
	}
	int reslong = ans[max].size();//记录找到的数组的长度
	//将所有的最大匹配集输出
	for (int k = 0; k < len; k++)
	{
		if (setsize[k] == reslong)//若是最大匹配集的长度就输出
		{
			printf("最大匹配集为:{");
			for (int i = 0; i < reslong; i++)
			{
				printf("e%d,", ans[k][i]);
			}
			printf("\b}\n");
		}
		
	}
	
	postman(n,m,grid);


    return 0;
}
//中国邮递员问题
//5 6
//1 0 0 1 0 1
//1 1 0 0 0 0
//0 1 1 0 0 0
//0 0 0 1 1 0
//0 0 1 0 1 1

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值