中国邮递员问题是一个和旅行商问题比较相关但又不太相同的一个问题,而且个人感觉理解的难度更大一点,当然,这就是仁者见仁,智者见智了,旅行商问题是不能回头的,一个节点访问过了不能回来了,并不需要走完所有的路,但是中国邮递员问题可以多次访问一个节点,因为中国邮递员问题要求的是要访问所有的街道,即,每条街道必须访问到。在这样的前提下,如果规划路径使得返回邮局时路程最短。

试想,假设每条路都走一次,最终恰好还能返回出发点,这样的拓扑图画出来应该满足怎样的结构呢?每个节点要进去还要出去,所以,每个点的入度和出度必须相等(起始点也一样),也就是说,每个节点的度应该是偶数个,满足这样的拓扑结构的图就是欧拉回路。如下图所示:

每个节点入度出度一定相同,所以总的度就是偶数。

但是,在实际生活中,或者实际拓扑中,节点的度很可能不是奇数个,因此,这也是解决中国邮递员问题最重要的一步,构造欧拉回路。

一个拓扑结构可以构造出很多个欧拉回路,但是,我们这道题要求的最短的路径,而原有的路径一定都是,所以,构造欧拉回路的边决定了最终的结果。

基于上述理解,判断是否为欧拉回路,如果是,直接相加返回结果,如果不是,那存在奇数度的节点一定有偶数个,如何选择更好的构造方案也成为了解决该问题第二个重要的点。

构造欧拉回路:

如上图所示,V8,V2,V4,V6都是奇数度点,因此,将这些点进行标记,来构造欧拉回路。

如下图所示,各种方案:

这几种方案都是构造欧拉回路的方案,但是,相对比会发现,只有最后一个是最优的结果,那如何得到最优结果呢,其实需要以下步骤来得到最优结果:

1、根据统计每个节点度的数目,标记出奇数度的节点。

2、奇数节点一定是有偶数个,因此,最终的距离应该是:两两点之间的最短距离,而两两点之间的最短距离可以用Floyd或者Dijkstra或者bellman-ford算法来得到。个人建议使用Floyd算法,不容易出错。

3、遍历所有的组合情况,求出最短的组合方式,例如比较 d(2,8)+d(4,6),d(2,6)+d(4,8), d(2,4)+d(6,8), 然后取最小值即为最终的优化方案:d(2,8)+d(4,6)。求解该步骤的时候,可以使用DFS来寻找最短路径方案,也可以利用DFS的思想,改为动态规划来实现,在别的博客上看到状压dp的字眼,应该是说这块的东西。动态规划思路的代码相对没有DFS更符合人们的思路,答题思路是构造一个dp表,行值为1,列值表示该集合中所含的元素,例如:集合7表示所含元素在奇度数组中下标为{1, 2, 3}的集合,即为二进制表示下,哪个位置为1,就含有对应的点,在这种方法下,为了方便操作,一般在邮递员问题中,点的编号是从1开始的,而求解的顺序应该是先求解小集合,再求解大集合,举个例子:假设现在得到的奇数度点的数组为:[2,4,6,8](为了代码方便,实际上该数组在后续代码的实现上是[0,2,4,6,8]),一共有四个有效点,则dp数组应为1×16的规模,dp[0]表示一个点也没有的集合,空集自然距离也为0,而更大集合j的最短距离值应该由更小的集合i及不在i中的两个点x,y求得,举个例子,当求出集合3({2, 4})的最短距离时,记录在dp[3]中,而集合15({2,4,6,8})的最短距离可以用dp[3]+d[6][8]来完成更新,如果dp[3]+d[6][8]<dp[15],则更新dp[15]。同理,当求出集合5({2,6})的最短距离时,记录在dp[5]中,则可以用dp[5]+d[4][8]<dp[15],则更新dp[15],该动态规划算法通过利用小数据集更新大数据集的距离,最终返回dp的最后一个值作为最优路径的结果。

最终的方案结果是:原图中的所有路径+构造时新添加的路径。

方案步骤:

1、生成邻接矩阵,利用Floyd算法求出每两个点之间的最短距离。(Floyd)

2、判断整个过程是否是欧拉回路,如果不是构造欧拉回路。

3、利用动态规划或者DFS计算构造欧拉回路的最优方案。

4、计算原路径与新构造路径的长度总和即为最终结果。

以下是参考代码:

步骤一:Floyd

    void Floyd (vector<vector<int>>& graph, int N) {for (int k=1; k<=N; ++k) {for (int i=1; i<=N; ++i) {for (int j=1; j<=N; ++j) {if (i == j) {continue;}if (graph[i][k]!=-1 && graph[k][j]!=-1) {graph[i][j] = graph[i][j]==-1 ? graph[i][k]+graph[k][j] : min (graph[i][j], graph[i][k]+graph[k][j]);}}}}}

这里要说明,我的程序中,-1表示路不通,所以加了一些判断条件,看上去比较复杂,其实也可以将数值定义成比较大的值,代码就比较简单,但是那样做有时候有越界危险。

步骤二:

    int shortestPath (vector<vector<int>>& graph, vector<int>& dev, int N) {Floyd (graph, N);vector<int> odds;odds.push_back(0);for (int i=1; i<=N; ++i) {if (dev[i]&1) {odds.push_back(i);}}int res = 0;int ods = odds.size()-1;vector<int> dp((1<<ods), -1);dp[0] = 0;for (int i=0; i<(1<<ods); ++i) {int x = 1;while ((1<<(x-1)) & i) {++x;}for (int y=x+1; y<=ods; ++y) {if ((1<<(y-1)) & i) {continue;}dp[i|(1<<(x-1))|(1<<(y-1))] = dp[i] != -1 && graph[odds[x]][odds[y]] != -1 ? dp[i|(1<<(x-1))|(1<<(y-1))] == -1 ? dp[i]+graph[odds[x]][odds[y]] : min(dp[i|(1<<(x-1))|(1<<(y-1))], dp[i]+graph[odds[x]][odds[y]]) : dp[i|(1<<(x-1))|(1<<(y-1))];}}for (int i=0; i<(1<<ods); ++i) {cout << dp[i] << " ";}cout << endl;cout << dp[(1<<ods)-1] << endl;return dp[(1<<ods)-1];}

该程序中,graph是已经求得的最短路径结果,数组dev记录的是所有节点的度,我们在下面的程序中,选择奇数度的节点组成odds数组,而后构建dp数组,通过小的集合i,以及不在i中的两点x和y计算更大的集合j的距离,通过多次迭代求出最优结果,因为我的dp初始化都是-1,也就是说-1在程序中的含义表示无限大,不可行的意思,所以要加一些比较细节的判断。

以下是整个程序的代码:

#include <bits/stdc++.h>using namespace std;class Solution {
private:void Floyd (vector<vector<int>>& graph, int N) {for (int k=1; k<=N; ++k) {for (int i=1; i<=N; ++i) {for (int j=1; j<=N; ++j) {if (i == j) {continue;}if (graph[i][k]!=-1 && graph[k][j]!=-1) {graph[i][j] = graph[i][j]==-1 ? graph[i][k]+graph[k][j] : min (graph[i][j], graph[i][k]+graph[k][j]);}}}}}public:int shortestPath (vector<vector<int>>& graph, vector<int>& dev, int N) {Floyd (graph, N);vector<int> odds;odds.push_back(0);for (int i=1; i<=N; ++i) {if (dev[i]&1) {odds.push_back(i);}}int res = 0;int ods = odds.size()-1;vector<int> dp((1<<ods), -1);dp[0] = 0;for (int i=0; i<(1<<ods); ++i) {int x = 1;while ((1<<(x-1)) & i) {++x;}for (int y=x+1; y<=ods; ++y) {if ((1<<(y-1)) & i) {continue;}dp[i|(1<<(x-1))|(1<<(y-1))] = dp[i] != -1 && graph[odds[x]][odds[y]] != -1 ? dp[i|(1<<(x-1))|(1<<(y-1))] == -1 ? dp[i]+graph[odds[x]][odds[y]] : min(dp[i|(1<<(x-1))|(1<<(y-1))], dp[i]+graph[odds[x]][odds[y]]) : dp[i|(1<<(x-1))|(1<<(y-1))];}}return dp[(1<<ods)-1];}
};int main()
{int N = 4, R = 5;//cin >> N >> R;//vector<vector<int>> routes(R, vector<int> (3, 0));vector<vector<int>> graph (N+1, vector<int> (N+1, -1));vector<int> dev (N+1, 0);int res = 0;//vector<vector<int>> routes(R, vector<int> (3, 0));vector<vector<int>> routes = {{1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {1, 4, 10}, {1, 3, 12}};for (int i=0; i<R; ++i) {int x = routes[i][0], y = routes[i][1], z = routes[i][2];//cin >> x >> y >> z;graph[x][y] = z;graph[y][x] = z;++dev[x];  ++dev[y];res += z;}cout << res << endl;Solution solve;res += solve.shortestPath (graph, dev, N);return 0;
}

中国邮递员问题+代码实现(cpp)相关推荐

  1. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | 查找 DexFile 对应的C代码 | dalvik_system_DexFile.cpp 分析 )

    文章目录 前言 一.查找 DexFile 对应的 C++ 代码 1.根据 Native 文件命名惯例查找 C++ 代码 2.根据方法名查找 二.dalvik_system_DexFile.cpp 源码 ...

  2. 【deep learning学习笔记】注释yusugomori的LR代码 --- LogisticRegression.cpp

    模型实现代码,关键是train函数和predict函数,都很容易. #include <iostream> #include <string> #include <mat ...

  3. 标定代码:CPP+OpenCV实现张正友标定法

    #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <ope ...

  4. FFmpeg通过摄像头实现对视频流进行解码并显示测试代码(新接口)

    在https://blog.csdn.net/fengbingchun/article/details/93975325 中给出了通过旧接口即FFmpeg中已废弃的接口实现通过摄像头获取视频流然后解码 ...

  5. FFmpeg通过摄像头实现对视频流进行解码并显示测试代码(旧接口)

    这里通过USB摄像头(注:windows7/10下使用内置摄像头,linux下接普通的usb摄像头(Logitech))获取视频流,然后解码,最后再用opencv显示.用到的模块包括avformat. ...

  6. FFmpeg中libswresample库简介及测试代码

    libswresample库功能主要包括高度优化的音频重采样.rematrixing和样本格式转换操作. 以下是测试代码(test_ffmpeg_libswresample.cpp),对音频了解较少, ...

  7. FFmpeg中libswscale库简介及测试代码

    libswscale库功能主要包括高度优化的图像缩放.颜色空间和像素格式转换操作. 以下是测试代码(test_ffmpeg_libswscale.cpp): #include "funset ...

  8. 经典网络LeNet-5介绍及代码测试(Caffe, MNIST, C++)

    LeNet-5:包含7个层(layer),如下图所示:输入层没有计算在内,输入图像大小为32*32*1,是针对灰度图进行训练和预测的.论文名字为" Gradient-Based Learni ...

  9. 提高C++性能的编程技术笔记:设计优化/可扩展性/系统体系结构相关+测试代码

    1. 设计优化 我们可以粗略地将性能优化分为两种类型:编码优化和设计优化.编码优化定义为不需要完整理解要解决的问题或者应用程序的执行流程就能实施的优化.通过定义看出,编码优化用于局部代码,同时该过程不 ...

  10. 提高C++性能的编程技术笔记:编码优化+测试代码

    缓存:在现代处理器中,缓存经常与处理器中的数据缓存和指令缓存联系在一起.缓存主要用来存储使用频繁而且代价高昂的计算结果,这样就可以避免对这些结果的重复计算.如,循环内对常量表达式求值是一种常见的低性能 ...

最新文章

  1. 线性回归 linear regression
  2. Invalid options object. Copy Plugin has been initialized using an options object that does not match
  3. struts2中文件上传
  4. jenkins 远程启动tomcat报错:Neither the JAVA_HOME nor the JRE_HOME environment variable is defined
  5. 电荷为什么不随运动而变化
  6. 阿里云马涛:云原生时代的开源操作系统长什么样
  7. C#反射(Reflection)对类的属性get或set值
  8. C++ 获取鼠标点击位置
  9. SPSS实现距离分析
  10. OBS视频采集流程分析
  11. idea git Untracked Files Prevent Pull
  12. msf介绍及其常用模块
  13. 优动漫PAINT拾色器功能介绍
  14. Photoshop脚本 设置前景色和背景色
  15. 嵌入式通过序列号加密总结及flash…
  16. Redis相关面试题
  17. 输入年份判断是否为闰年
  18. Mysql DATE_FORMAT 用法
  19. 7.25~7.26 周末翻倍奖励——滴滴快车单
  20. 使用 Java 8 语言功能

热门文章

  1. 罗升阳 51test 博客
  2. 如何下载衡水市卫星地图高清版大图
  3. TDA4 制作 SD卡驱动
  4. 【知识管理】知识管理系统功能构件简介
  5. zxing绘制条形码总结
  6. 数字交通灯设计(Multisim仿真+PCB实物)
  7. 建材物资管理系统(软件定义)
  8. 网络安全事件收集,分析
  9. 移动端开发旅游预约_套餐列表页面动态展示_套餐详情页面动态展示
  10. 一个炫酷的前端导航网站