旅行商问题(枚举,回溯,动态规划,贪心,分支界限)

文章目录

  • 问题描述
  • 暴力枚举
  • 回溯法
  • 动态规划法
  • 贪心法
  • 分支界限法

问题描述

假设有一个货郎担要拜访n个城市,他必须选择所要走的路程,路程的限制时每个城市只能拜访一次,而且最后要走到原来出发的城市,要求路径长度。

在这里插入图片描述

旅行商问题将要走过的城市建立成一个完全图。稠密图,所以用临接矩阵来存。
由于路径的特殊性,可以正走也可以反着走,所以一般存在两条最优路径同时也可以用这条性质检验算法的正确性。

暴力枚举

使用dfs枚举每一个点, 不适用剪枝的话就是暴力枚举方法

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>using namespace std;const int N = 10;int g[N][N], n, m;
int cv = 0, bv = 0x3f3f3f3f;
bool st[N];vector<int> ans, x;void dfs(int k)
{if (k == n){// printf("before cv : %d\n", cv);// printf("last : {%d, %d} = %d\n", 1, x[k - 1], g[1][x[k - 1]]);cv += g[1][x[k - 1]];x.push_back(x[0]);for (auto i : x)printf("%d ", i);puts("");printf("{cv : %d}\n", cv);if(cv < bv){bv = cv;ans = x;}cv -= g[1][x[k - 1]];//注意最后一个加的cv要减掉x.pop_back();//同样也要删掉return;}for (int i = 1; i <= n; i ++){if (!st[i]){st[i] = true;x.push_back(i);//注意x的添加要在前面不然后面下标会出错cv += g[x[k - 1]][i];// printf("{%d, %d} : %d\n", x[k - 1], i,  g[x[k - 1]][i]);dfs(k + 1);cv -= g[x[k - 1]][i];x.pop_back();st[i] = false;}}
}void out()
{puts("路径为:");for (int i = 0; i <= n; i ++){printf("%d", ans[i]);printf(i == n ? "\n" : "->");}
}int main()
{memset(g, 0x3f, sizeof g);scanf("%d%d", &n, &m);for (int i = 0; i < m; i ++){int a, b, c;scanf("%d%d%d", &a, &b, &c);g[a][b] = g[b][a] = min(g[a][b], c);}for (int i = 0; i <= n; i ++) g[i][i] = 0; st[1] = true; x.push_back(1);dfs (1);puts("最短路径为:");printf("%d\n", bv);out();reverse(ans.begin(), ans.end());out();puts("");return 0;
}

在这里插入图片描述

回溯法

回溯法就是在暴力枚举的是后加上剪枝函数减少枚举的结点数目
剪枝函数为
左剪枝:
当 c v > b v 时减去 当cv > bv时减去 cv>bv时减去
在这里插入图片描述
在暴力枚举的基础上加上这个剪枝函数就行

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>using namespace std;const int N = 10;int g[N][N], n, m;
int cv = 0, bv = 0x3f3f3f3f;
bool st[N];vector<int> ans, x;void dfs(int k)
{if (k == n){// printf("before cv : %d\n", cv);// printf("last : {%d, %d} = %d\n", 1, x[k - 1], g[1][x[k - 1]]);cv += g[1][x[k - 1]];x.push_back(x[0]);for (auto i : x)printf("%d ", i);puts("");printf("{cv : %d}\n", cv);if(cv < bv){bv = cv;ans = x;}cv -= g[1][x[k - 1]];//注意最后一个加的cv要减掉x.pop_back();//同样也要删掉return;}for (int i = 1; i <= n; i ++){if (!st[i]){st[i] = true;x.push_back(i);cv += g[x[k - 1]][i];// printf("{%d, %d} : %d\n", x[k - 1], i,  g[x[k - 1]][i]);if (cv <= bv)dfs(k + 1);cv -= g[x[k - 1]][i];x.pop_back();st[i] = false;}}
}void out()
{puts("路径为:");for (int i = 0; i <= n; i ++){printf("%d", ans[i]);printf(i == n ? "\n" : "->");}
}int main()
{memset(g, 0x3f, sizeof g);scanf("%d%d", &n, &m);for (int i = 0; i < m; i ++){int a, b, c;scanf("%d%d%d", &a, &b, &c);g[a][b] = g[b][a] = min(g[a][b], c);}for (int i = 0; i <= n; i ++) g[i][i] = 0; st[1] = true; x.push_back(1);dfs (1);puts("最短路径为:");printf("%d\n", bv);out();reverse(ans.begin(), ans.end());out();puts("");return 0;
}

搜索的结点数变成了
在这里插入图片描述
相比穷举减少了搜索的结点数

动态规划法

状态压缩dp
利用一个int位中的32位0/1bit码来表示图走了哪些点,如果此位为1表示经过,0表示还未经过

类似题目AcWing 91. 最短Hamilton路径

在这里插入图片描述

/*由于要遍历每一个点所以不能用最短路径算法从一个已知点到另一个点只需要关注两个状态:1、终点是什么, 2、经过了哪些点而dp[i][j] 表示从0到终点j,经过了二进制状态(每个点有走过和没走两个状态)的点的路径状态计算:dp[i][j] <- dp[i - j][k](在已经经过的点中,去掉点j的方案取最小)
*/
#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N = 21, M = 1 << N;
int dp[M][N];
int w[N][N];
int n;int main()
{scanf("%d", &n);for (int i = 0; i < n; i ++)for (int j = 0; j < n; j ++)scanf("%d", &w[i][j]);memset(dp, 0x3f, sizeof dp);dp[1][0] = 0;for (int i = 0; i < 1 << n; i ++)for (int j = 0; j < n; j ++)if (i >> j & 1)for (int k = 0; k < n; k ++ )if (i >> k & 1)dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + w[j][k]);// 转移到达第i个点时将第i个点的状态要去掉就// 例如要将100101的第2个点去掉就需要 - 000100 = 100001printf("%d\n", dp[(1 << n) - 1][n - 1]);// 100000 - 000001 = 0111111 要将n - 1位全置位为1只需要用n为1后面为0减个位1即可return 0;
}

贪心法

贪心就是从起点开始每次走从这个点出发权重最小的边
但是这个寻找局部最优解的过程找到的并不是全局最优解
思路和生成最小生成树的思路一样,由于是完全图稠密图,所以使用prim算法更好

#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N = 510, INF = 0x3f3f3f3f;int g[N][N], dist[N];
bool st[N];
int n, m;
vector<int> ans;int prime()
{memset(dist, 0x3f, sizeof dist);int res = 0;// 最小生成树中的权重之和for (int i = 0; i < n; i ++){int t = -1;// t表示集合外与集合相连的边最小的结点for (int j = 1; j <= n; j ++)if (!st[j] && (t == -1 || dist[j] < dist[t]))// 集合外的,第一次直接赋值,值更小的t = j;st[t] = true;// 加入集合ans.push_back(t);if (i && dist[t] == INF) return INF;// 不是第一个节点且到集合的距离无穷,说明各个结点都不连通if (i) res += dist[t];for (int j = 1; j <= n; j ++)dist[j] = min (dist[j], g[t][j]);// 更新与集合相连的最小值}return res;
}void out()
{puts("路径:");for (int i = 0; i <= n; i ++){printf("%d", ans[i]);printf(i == n ? "\n" : "->");}
}int main()
{cin >> n >> m;memset(g, 0x3f, sizeof g);for (int i = 0; i < m; i ++){int a, b, c;scanf("%d%d%d", &a, &b, &c);g[a][b] = g[b][a] = min (g[a][b], c);// 无向图要将两个方向的边都赋上权重}int res = prime();if (res == INF) puts("impossible");else printf("%d\n", res + g[ans[n - 1]][1]);ans.push_back(ans[0]);out();reverse(ans.begin(), ans.end());out();return 0;
}

在这里插入图片描述

分支界限法

使用优先队列形式
cc为当前代价,rc为剩余结点的最小出边代价和
下界函数为: cc + rc
左剪枝:
当 c c > b c 时剪枝 当cc > bc时剪枝 cc>bc时剪枝
右剪枝:
当 c c + r c > b c 是剪枝 当cc + rc > bc是剪枝 cc+rc>bc是剪枝

归结左右剪枝都可以用bound = cc + rc进行剪枝

剩余结点最小出边代价和就是枚举剩余每条结点对没给结点只算最小的出边

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>using namespace std;const int N = 16;
const int INF = 0x3f3f3f3f;int g[N][N], n, m, bc = INF;
vector<int> ans;struct Node {int idx, cost, bound;vector<int> path;bool st[N];bool operator<(const Node& other) const {return bound + cost > other.bound + other.cost;  // 按照 bound + cost 降序排序}
};int bound(const Node& x) {int minCost = 0;for (int i = 1; i <= n; ++i) {if (x.st[i]) {int m = INF;for (int j = 1; j <= n; ++j) {if (x.st[j]) {m = min(m, g[i][j]);}}minCost += m;}}return minCost;
}void bfs() {priority_queue<Node> heap;Node head = {1, 0, 0, {1}, {false}};head.st[1] = true;heap.push(head);while (heap.size()) {auto t = heap.top();heap.pop();if (t.idx == n) {int cc = t.cost + g[t.path[t.idx - 1]][1];for (auto i : t.path)printf("%d ", i);printf("%d", 1);puts("");if (cc < bc){bc = cc;ans = t.path;}continue;}for (int i = 1; i <= n; ++i) {if (!t.st[i]) {Node newNode = t;newNode.st[i] = true;newNode.path.push_back(i);newNode.cost += g[newNode.path[newNode.idx - 1]][i];newNode.idx++;newNode.bound = bound(newNode); if(newNode.bound < bc)//左右剪枝通用,因为是排列树左右都要算下界函数heap.push(newNode);}}}
}void out()
{puts("路径:");for (int i = 0; i <= n; i ++){printf("%d", ans[i]);printf(i == n ? "\n" : "->");}
}int main() {memset(g, 0x3f, sizeof g);scanf("%d%d", &n, &m);for (int i = 0; i < m; ++i) {int a, b, c;scanf("%d%d%d", &a, &b, &c);g[a][b] = g[b][a] = min(g[a][b], c);}bfs();printf("%d\n", bc);ans.push_back(ans[0]);out();reverse(ans.begin(), ans.end());out();return 0;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/212113.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring配置其他注解Spring注解的解析原理

Spring配置其他注解 Primary注解用于标注相同类型的Bean优先被使用权&#xff0c;Primary是Spring 3.0引入的&#xff0c;与Component和Bean一起使用&#xff0c;标注该Bean的优先级更高&#xff0c;则在通过类型获取Bean或通过Autowired根据类型进行注入时&#xff0c;会选用优…

【Python】np.unique() 介绍与使用

简述 numpy.unique&#xff1a;用于去除数组中重复元素&#xff0c;并从小到大排序&#xff08;找到唯一元素并排序&#xff09;。 def unique(ar, return_indexFalse, return_inverseFalse,return_countsFalse, axisNone):ar: 这是输入的数组或类数组对象。return_index: 如…

红队攻防实战之内网穿透隐秘隧道搭建

别低头&#xff0c;皇冠会掉&#xff1b;别流泪&#xff0c;贱人会笑。 本文首发于先知社区&#xff0c;原创作者即是本人 0x00 前言 构建内网隐蔽通道&#xff0c;从而突破各种安全策略限制&#xff0c;实现对目标服务器的完美控制。 当我们从外网成功获得攻击点的时候&…

YOLO目标检测——泄露检测数据集下载分享【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;泄露检测数据集说明&#xff1a;泄露检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;含多个类别标签说明&#xff1a;使用lableimg标注软件标注&#xff0c;标注框质量高&#xff0c;含voc(xml)、coco(json)和yo…

【double check 读写锁】

使用double check 读写锁 读多写少场景 记录下 //来源 jdbc 中的查询连接信息 //public abstract class ConnectionUrl implements DatabaseUrlContainer public static ConnectionUrl getConnectionUrlInstance(String connString, Properties info) {if (connString null…

c++|类和对象(上)

目录 一、面向过程和面向对象初步认识 二、类的引入和定义 2.1类的引入 2.2类的定义 三、类的访问限定符及封装 3.1访问限定符 3.2封装 四、类的作用域 五、类的实例化 六、类的对象大小的计算 6.1如何计算对象的大小 6.2类对象的存储方式 七、类成员函数的thi…

Leetcode 205. 同构字符串

文章目录 题目代码&#xff08;11.23 手刷看解析&#xff09; 题目 Leetcode 205. 同构字符串 代码&#xff08;11.23 手刷看解析&#xff09; 知识点 数组的长度是.length&#xff0c;是一个属性&#xff0c;其他数据类型是函数方法&#xff0c;要用.length()String的方法&…

C++初阶 | [五] 内存管理

摘要&#xff1a;new and delete&#xff0c;定位new&#xff0c;&#xff08;C内存管理的方式&#xff09;&#xff0c;malloc/free和new/delete的区别&#xff0c;内存泄漏 关于内存&#xff1a; 栈又叫堆栈——非静态局部变量/函数参数/返回值等等&#xff0c;栈是向下增长…

excel单元格加背景颜色不生效?

如果在 Excel 中设置单元格背景颜色而发现不生效&#xff0c;可能有几个原因。以下是一些常见的解决方法&#xff1a; 1. **单元格锁定&#xff1a;** 检查所在单元格是否被锁定。如果单元格被锁定&#xff0c;并且工作表被保护&#xff0c;你可能无法更改其背景颜色。在工作表…

批量插入SQL 错误 [933] [42000]: ORA-00933: SQL 命令未正确结束

使用DBeaver向【oracle数据库】插入大量数据 INSERT INTO Student(name,sex,age,address,birthday) VALUES(Nike,男,18,北京,2000-01-01) ,(Nike,男,18,北京,2000-01-01) ,(Nike,女,18,北京,2000-01-01) ,(Nike,女,18,北京,2000-01-01) ,(Nike,男,18,北京,2000-01-01) ,(Nike…

【算法】缓存淘汰算法

目录 1.概述2.代码实现2.1.FIFO2.2.LRU2.3.LFU2.4.Clock2.5.Random 3.应用 1.概述 缓存淘汰策略是指在缓存容量有限的情况下&#xff0c;当缓存空间不足时决定哪些缓存项应当被移除的策略。缓存淘汰策略的目标是尽可能地保持缓存命中率高&#xff0c;同时合理地利用有限的缓存…

筑牢思想防线——建行驻江门市分行纪检组举办2023年清廉合规大讲堂

为推动廉洁教育打通“最后一公里”&#xff0c;近日&#xff0c;建行驻江门市分行纪检组举办江门市分行2023年清廉合规大讲堂。 本次大讲堂检察官结合一线办案经历&#xff0c;从防范化解金融风险、预防金融从业人员犯罪等方面对全辖员工进行了深入浅出地的讲解&#xff0c;引导…