中国邮递员问题
1. 问题描述
中国邮递员问题是邮递员在某一地区的信件投递路程问题。邮递员每天从邮局出发,走遍该地区所有街道再返回邮局,问题是他应如何安排送信的路线可以使所走的总路程最短。
2. 问题解决
图本身就是一个欧拉回路图,那么直接走一个欧拉回路就访问了所有路径并回到了起点
(欧拉回路图的前提条件是每个点的度数都是偶数)
图不是一个欧拉回路图,需要对某些边重复走多次回到起点,等价于添加了一些已经存在的重边,构建了欧拉回路。
不是欧拉回路需要通过增加一些边使得图变成一个欧拉回路,并能保证问题的最优解,添加的边权值和一定是尽可能短的。
- 计算度数为奇数的点
- 算出所有点之间的最短路径
- 奇度数的点一定为偶数,点与点之间构建二分图,权值为两点之间的最短路,找到一个最小权值匹配集
- 对于匹配集,找到每一个匹配集两点之间形成的最短路径,那么这条最短路径就需要加入到额外的边中,且一定能保证这条路径上除了这两个匹配的奇度数点度数加了1变成了偶数,其他所有中间点都是加了2,不影奇偶性。
- 所有边都添加好后,就是一个欧拉回路了,计算从起点开始的欧拉回路路径
3.相关算法
Fleury算法:求解欧拉回路路径,可以写成非常简洁的形式
123456789101112131415dfs(source , euler_path);void dfs(int u , vector<pii> &euler_path){for(int i=0 ; i<edges[u].size() ; i++){int v = edges[u][i];if(0 < euler_edge_degree[u][v]){// cout<<"here debug: "<<u<<" -- "<<v<<endl;euler_edge_degree[u][v] -- ;euler_edge_degree[v][u] -- ;dfs(v , euler_path);euler_path.push_back(make_pair(u, v));}}}Floyd计算最短路算法
123456void floyd(){for(int i=0 ; i<n ; i++)for(int j=0 ; j<n ; j++)for (int k=0 ; k<n ; k++)min_dis[i][j] = min(min_dis[i][j] , min_dis[i][k]+min_dis[k][j]);}最大权值匹配KM算法(算最小权匹配,只要将权值设置为负数就可以了)
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455// 这里主要通过mp[i][j]存储连接的权值,下标从0开始,这部分代码没有考虑左右两点存在无权值的情况int weight[N][N],match[N],lx[N],ly[N],visx[N],visy[N],slack[N];int n , m; //n表示左侧二分图的节点个数,m表示右侧二分图节点个数。bool dfs(int x){visx[x]=1;for(int y=0 ; y<n ; y++){if(visy[y]) continue;int t=lx[x]+ly[y]-weight[x][y];if(t==0){visy[y]=1;if(match[y]==-1||dfs(match[y])){match[y]=x;return true;}}else if(slack[y]>t) slack[y]=t;}return false;}// KM() 返回的是最大权匹配,匹配结果保存在match[]中,可通过get_match_pair得到,要计算最小权匹配,初始化的时候权值加个负号就可以了int KM(){memset(match,-1,sizeof(match));memset(lx,-INF,sizeof(lx));memset(ly,0,sizeof(ly));for(int i=0;i<n;i++){for(int j=0;j<n;j++){if(weight[i][j]>lx[i]) lx[i]=weight[i][j];}}for(int i=0;i<n;i++){for(int y=0;y<n;y++)slack[y]=INF;while(true){memset(visx,0,sizeof(visx));memset(visy,0,sizeof(visy));if(dfs(i)) break;int d=INF;for(int y=0;y<n;y++){if(!visy[y]&&d>slack[y]) d=slack[y];}for(int x=0;x<n;x++){if(visx[x]) lx[x]-=d;}for(int y=0;y<n;y++){if(visy[y]) ly[y]+=d;else slack[y]-=d;}}}int res=0;for(int i=0;i<n;i++){if(match[i]>-1) res+=weight[match[i]][i];}return res;}找到两点之间的最短路径Dijkstra算法
123456789101112131415161718192021222324252627282930313233343536373839int dis[N][N] , n;// 找到x和y之间的最短路径,用 <<x,y>,w>的方式存下来vector<piii> find_shortest_path(int x , int y){cout<<"debug find shortest path: "<<x<<" -- "<<y<<endl;vector<piii> shortest_path = vector<piii>();int lastpos[N];int optimal[N];memset(lastpos , -1 , sizeof(lastpos));memset(optimal , 0x3f , sizeof(optimal));set<pii> st;st.insert(make_pair(0 , x));optimal[x] = 0;while(!st.empty()){pii u = *(st.begin());st.erase(st.begin());// cout<<"debug dijkstra: "<<u.Y<<" : "<<u.X<<endl;if(u.Y == y) break;if(u.X > optimal[u.Y]) continue;for(int v=0 ; v<n ; v++){if(dis[u.Y][v] != INF){if(u.X + dis[u.Y][v] < optimal[v]){optimal[v] = u.X + dis[u.Y][v];lastpos[v] = u.Y;st.insert(make_pair(optimal[v] , v));}}}}int cur = y;while(lastpos[cur] != -1){// cout<<"debug last pos: "<<cur<<endl;shortest_path.push_back(make_pair(make_pair(lastpos[cur], cur), dis[lastpos[cur]][cur]));cur = lastpos[cur];}// cout<<"debug 3 section finish"<<endl;return shortest_path;}
4.整体代码实现
|
|