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

news2024/9/24 18:05:47

文章目录

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

问题描述

假设有一个货郎担要拜访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 = 100001
                        
    printf("%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.coloradmin.cn/o/1243878.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言第二十五弹--打印菱形

C语言打印菱形 思路&#xff1a;想要打印一个菱形&#xff0c;可以分为上下两部分&#xff0c;通过观察可以发现上半部分星号的规律是 1 3 5 7故理解为 2对应行数 1 &#xff0c;空格是4 3 2 1故理解为 行数-对应行数-1。 上半部分代码如下 for (int i 0;i < line;i){//上…

【C/PTA】函数专项练习(四)

本文结合PTA专项练习带领读者掌握函数&#xff0c;刷题为主注释为辅&#xff0c;在代码中理解思路&#xff0c;其它不做过多叙述。 目录 6-1 计算A[n]1/(1 A[n-1])6-2 递归实现顺序输出整数6-3 自然数的位数(递归版)6-4 分治法求解金块问题6-5 汉诺塔6-6 重复显示字符(递归版)…

git 提交成了LFS格式,如何恢复

平常习惯使用sourceTree提交代码&#xff0c;某次打开时弹出了一个【是否要使用LFS提交】的确认弹窗&#xff0c;当时不知道LFS是什么就点了确认&#xff0c;后续提交时代码全变成了这个样子 因为是初始化的项目首次提交&#xff0c;将近四百个文件全被格式化成了这个样子&…

Python 使用tkinter复刻Windows记事本UI和菜单功能(三)

上一篇&#xff1a;Python 使用tkinter复刻Windows记事本UI和菜单功能&#xff08;二&#xff09;-CSDN博客 下一篇&#xff1a;敬请耐心等待&#xff0c;如发现BUG以及建议&#xff0c;请在评论区发表&#xff0c;谢谢&#xff01; 本文章完成了记事本的新建、保存、另存、打…

为什么 x86 操作系统从 0x7c00 处开始

0x00&#xff1a;x86 架构 BIOS 引导加载程序中的"0x7C00"之谜 你知道 x86 操作系统中的"0x7C00"这个神奇数字吗 ? "0x7C00" 是BIOS加载MBR&#xff08;主引导记录&#xff0c;磁盘中的第一个扇区&#xff09;的内存地址。操作系统或引导加载…

思维模型 等待效应

本系列文章 主要是 分享 思维模型 &#xff0c;涉及各个领域&#xff0c;重在提升认知。越是等待&#xff0c;越是焦虑。 1 等待效应的应用 1.1 等待效应在管理中的应用 西南航空公司是一家美国的航空公司&#xff0c;它在管理中运用了等待效应。西南航空公司鼓励员工在工作中…

XUbuntu22.04之解决gpg keyserver receive failed no data(一百九十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Axios 通过a标签下载文件 跨域下载

<!-- a标签占位 --><a ref"down" ></a>getTest() {this.$axios.request({url: https://cnv13.55.la/download?file_key3695fa9461a0ae59cf3148581e4fe339&handle_typeexcel2pdf,method: get,responseType: blob, // 切记类型 blob}).then(re…

【Java并发】聊聊线程池原理以及实际应用

线程其实对于操作系统来说是宝贵的资源&#xff0c;java层面的线程其实本质还是依赖于操作系统内核的线程进行处理任务&#xff0c;如果频繁的创建、使用、销毁线程&#xff0c;那么势必会非常浪费资源以及性能不高&#xff0c;所以池化技术&#xff08;数据库连接池、线程池&a…

3D火山图绘制教程

一边学习&#xff0c;一边总结&#xff0c;一边分享&#xff01; 本期教程内容 **注&#xff1a;**本教程详细内容 Volcano3D绘制3D火山图 一、前言 火山图是做差异分析中最常用到的图形&#xff0c;在前面的推文中&#xff0c;我们也推出了好几期火山图的绘制教程&#xff0…

Linux下载工具XDM下载安装与使用

Windows上IDM多线程下载非常强大&#xff0c;即能捕捉页面上的视频、图片、音频&#xff0c;又能作为浏览器下载器使用&#xff0c;但是IDM无法在Linux下使用&#xff0c;除非使用wine。不过我们可以在Linux中用XDM(Xtreme Download Manager)代替IDM。 1、XDM下载 Xtreme Dow…

从Discord的做法中学习 — 使用Golang进行请求合并

正如你可能之前看到的&#xff0c;Discord去年发布了一篇有价值的文章&#xff0c;讨论了他们成功存储了数万亿条消息。虽然有很多关于这篇文章的YouTube视频和文章&#xff0c;但我认为这篇文章中一个名为“数据服务为数据服务”的部分没有得到足够的关注。在这篇文章中&#…

如何在AD的PCB板做矩形槽孔以及如何倒圆弧角

Altium Designer 22下载安装教程-CSDN博客 如何在AD上创建完整的项目-CSDN博客 开始前&#xff0c;请先安装后AD&#xff0c;并创建好项目。 目录 1. 如何在AD的PCB板做矩形槽孔 2. 如何在AD的PCB板倒圆弧角 1. 如何在AD的PCB板做矩形槽孔 首先&#xff0c;我们进入上面创…

普通话考试相关(一文读懂)

文章目录&#xff1a; 一&#xff1a;相关常识 1.考试报名时间 2.报名地方 费用 证件 3.考试流程 4.普通话等级说明 二&#xff1a;题型 三&#xff1a;技巧 1.前三题 2.命题说话 四&#xff1a;普通话考试题库 1.在线题库 2.下载题库 一&#xff1a;相关常识 …

【工具栏】热部署不生效

目录 配置热部署&#xff1a; 解决热部署不生效&#xff1a; 首先检查&#xff1a; 第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a; 第四步&#xff1a; 配置热部署&#xff1a; https://blog.csdn.net/m0_67930426/article/details/133690559 解决热部署不…

OmniGraffle

安装 在mac上安装OmniGraffle&#xff0c;找一个正版或者啥的都行&#xff0c;安装好后&#xff0c;可以直接在网上找一个激活码&#xff0c;然后找到软件的许可证&#xff0c;进行添加即可。 使用 新建空白页 然后图形啥的看一眼工具栏就知道了&#xff0c;颜色形状还是挺…

ELK企业级日志分析平台——ES集群监控

启用xpack认证 官网&#xff1a;https://www.elastic.co/guide/en/elasticsearch/reference/7.6/configuring-tls.html#node-certificates 在elk1上生成证书 [rootelk1 ~]# cd /usr/share/elasticsearch/[rootelk1 elasticsearch]# bin/elasticsearch-certutil ca[rootelk1 ela…

九、ffmpeg命令转封装

开了几天小差&#xff0c;今天继续学习ffmpeg。 准备测试使用的视频&#xff0c;并查看其信息 # 查看视频信息。使用Mediainfo也可以 ffprobe test.mp4 视频格式的信息如下。 保持编码格式&#xff1a;ffmpeg -i test.mp4 -vcodec copy -acodec copy test_copy.tsffmpeg -i…

读书笔记——《黑猩猩的政治》

前言 弗朗斯德瓦尔&#xff08;Frans de Waal)的代表作《黑猩猩政治》成书于1982年&#xff0c;是它的首部书籍作品&#xff0c;也是美国国会新任议员的被推荐读物。之前看的他另一部作品的《万智有灵》是2016年的作品&#xff0c;时间跨度居然这么大。《万智有灵》介绍了许多…

6.2.SDP协议

那今天呢&#xff1f;我们来介绍一下sdp协议&#xff0c;那实际上呢&#xff1f;sdp协议非常的简单。我们如果拿到一个stp的文档去看的话&#xff0c;那你要分阅里边的所有的内容会觉得很枯燥&#xff0c;但实际上呢&#xff0c;如果我们按照这张图所展示的结构去看stp的话。你…