c++ 旅行商问题(动态规划)

news2024/9/22 23:23:09

目录

  • 一、旅行商问题简介
    • 旅行商问题
    • 问题概述
    • 问题由来
  • 二、基本思路
  • 三、实现
    • 1、状态压缩
    • 2、状态转移
  • 四、代码
  • 复杂度分析

一、旅行商问题简介

旅行商问题

  TSP,即旅行商问题,又称TSP问题(Traveling Salesman
Problem),是数学领域中著名问题之一。

问题概述

  假设有一个旅行商人要拜访N个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。TSP问题是一个NPC问题。

问题由来

  TSP的历史很久,最早的描述是1759年欧拉研究的骑士周游问题,即对于国际象棋棋盘中的64个方格,走访64个方格一次且仅一次,并且最终返回到起始点。

  TSP由美国RAND公司于1948年引入,该公司的声誉以及线形规划这一新方法的出现使得TSP成为一个知名且流行的问题。

示例:
在这里插入图片描述

黑色数字代表点、红色代表路径的花费

输入:

4 6
1 2 1
1 4 2
1 3 4
2 3 1
2 4 2
3 4 3

输出:

运行中...

最短距离为:72条最短路径:
路径11-->4-->3-->2-->1
路径21-->2-->3-->4-->1

提示:

第一行输入点的个数n和边的个数m,点的编号为1~n
接下来m行输入m条边以及花费,p1 p2 v,表示点p1和点p2之间有一条无向边,边的花费为v

二、基本思路

  1、我们需要知道,我们求的路径是一个环,所以无论从哪里开始,结果都应该是一样的,就像例题中的;
最短路径可表示为 1–>4–>3–>2–>1
那么它也可以表示为 4–>3–>2–>1–>4
还可以表示为 3–>2–>1–>4–>3

所以我们可以从任意的点出发去查找路径

  2、旅行商问题只有当图是哈密顿图时才可能有解的,即需要满足题意,可以从一个点出发,到达所有的点一次,然后回到起点。

这个可以通过最后运行的结果判断,我们令初始答案是一个很大的值,如果查找后答案没有被改变,则该图无解

  3、按照传统的暴力搜索,时间复杂度为O(n!),而动态规划可以将复杂度减低到O(n2*2n)

  4、有个注意的点,起点需要走两遍,为了简化问题,只需要预处理从起点走到其它点的最小花费,而起点不能标记为已经走过,因为后面还有回到原点

三、实现

1、状态压缩

  我们需要表达我们已经走过了哪些点,目前到达了哪里,有什么办法表达出来呢?

  暴力是万能的,我们可以开一个数组dp[i][j],代表目前到达了i点,dp[i][j]的值代表j点是否已经走过了,但是这样做的话我们状态转移会变得很麻烦,状态压缩就是它的优化

  状态压缩是通过二进制实现的,我们知道int有32位,那么我们可以用第0位代表第0个点的状态,第1位代表第1个点状态…第n位代表第n个点的状态,位的值如果是1的话就代表该点已经走过了,例如17的二进制为0000010001,代表第0个点和第4个点已经走过了

  那么我们可以开一个数组dp[i][j],代表目前走到了i点,用j代表已经哪些点,例如:
dp[0][17],17的二进制为0000010001,代表目前在第0个点,已经走过第0个点和第4个点。
dp[16][17],17的二进制为0000010001,代表目前在第4个点,已经走过第0个点和第4个点。

  我们可以用dp[i][j]的值代表当前这个状态的最小花费,例如dp[0][17]=12,那么就代表到达该状态需要的最小花费是12

2、状态转移

  dp的基本思想就是记录某个状态的最优解,再从目前的状态转移到新的状态,从局部最优解转移到全局最优解

  我们用数组a[i][j]存储图,那么a[i][j]的值就代表从i点到j点的花费

  我们如何求状态dp[0][19]的最优解?
  19的二进制是0000010011,因为18的二进制为0000010010,那么dp[0][19]可以由dp[4][18],dp[2][18]转移过来,最小花费是dp[0][19]=min(dp[4][18]+a[4][0],dp[2][18]+a[2][0])

  即我们要求大的状态,那么就需要先把小状态最优解求出来。反过来我们求出了所有小状态,那么就可以求出大状态的最优解

在这里插入图片描述

  {1,2,3}代表第1、2、3个点都已经走过了

  可以发现,小状态总是比大状态小的,那么我们可以从0状态枚举到2n-1状态,获取到每个状态的最优解

我们还可以反过来想,从小状态去更新大的状态
在这里插入图片描述
  两种思路都可以

四、代码

  下面代码是基于逆向思想的,即从小状态更新大状态。理解透了的同学不妨尝试写一下大状态调用小状态更新的代码

#include<bits/stdc++.h>
using namespace std;
int n,m;//n点的个数,m边的个数 
int a[15][15];//邻接矩阵存无向图 
int dp[15][1<<15];//dp[i][j]代表从最后走到i点到达状态j 
int t;//一共有t个状态 


void init(){//初始化 
	memset(a,0x3f,sizeof a);
	memset(dp,0x3f,sizeof dp);
	cout<<"请输入点和边的个数:"<<endl;
	cin>>n>>m;
	cout<<"请输入"<<m<<"条边:"<<endl;
	for(int i=0;i<m;i++){
		int x,y,val; 
		cin>>x>>y>>val;
		x--;
		y--;
		a[x][y]=val;
		a[y][x]=val;
	}
} 


void run(){//dp核心算法 
	t=(1<<n);
	for(int i=1;i<n;i++){//因为起点初始不能被标记已经走过,所以需要手动初始化起点到达其它点的花费 
		dp[i][1<<i]=a[0][i];
	}
	for(int i=0;i<t;i++){//枚举每一个状态 
		for(int j=0;j<n;j++){//枚举每一个没有走过的点 
			if(((i>>j)&1)==0){
				for(int k=0;k<n;k++){//枚举每一个走过的点 
					if(((i>>k)&1)==1&&dp[j][i^(1<<j)]>dp[k][i]+a[k][j]){//取最优状态 
						dp[j][i^(1<<j)]=dp[k][i]+a[k][j];
					}
				}
			}
		}
	}
}

int tt;//记录 
vector<int> path(1,0);//初始化从0点出发 ,存储单条路径 
vector<vector<int> > paths;//存储所有的路径 
void getPath(int p){//递归查找所有路径 
	if((tt^(1<<p))==0){//如果是最后一个点了就存储改路径 
		paths.push_back(path);
		return; 
	}
	for(int j=1;j<n;j++){
		//回溯算法,一个加法的原则
		//如果点1到达点5的最短距离为100,点1到达点3的最短距离是70
		//而点3和点5之间的距离为30 ,那么点3是点1到5之间的一个中间点
		//即1-->...-->3-->5 
		if(a[j][p]+dp[j][tt^(1<<p)]==dp[p][tt]){
			tt^=(1<<p);
			path.push_back(j);
			getPath(j);
			tt^=(1<<p);
			path.pop_back();
		}
	}
	
} 

void print(){//打印路径 
	cout<<"最短距离为:"<<dp[0][t-1]<<endl;
	cout<<"共"<<paths.size()<<"条最短路径:" <<endl; 
	for(int i=0;i<paths.size();i++){
		cout<<"路径"<<i+1<<":1";
		for(int j=paths[i].size()-1;j>=0;j--){
			cout<<"-->"<<paths[i][j]+1;
		}
		cout<<endl;
	}
}

int main(){
	init();
	cout<<"运行中..."<<endl<<endl; 
	run(); 
	cout<<"运行结果:"<<endl; 
	
	if(dp[0][t-1]==0x3f3f3f3f){//无解 
		cout<<"该图不是哈密顿图!"<<endl;
		return 0;
	}
	
	tt=t-1;
	getPath(0);
	
	print(); 
} 

复杂度分析

  时间复杂度: 求最小花费枚举2n种状态,每种状态枚举每一个没有走过的点,每一个没走过的点需要枚举每一个已经走过的点,时间复杂度O(n2*2n),求所有路径,时间复杂度将退化为O(n!)
  空间复杂度: 记录每个点的2n种状态,空间复杂度O(n*2n)

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

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

相关文章

网络编程基础知识

文章目录1、网络概念2、协议3、网络分层4、网络传输流程5、端口号1、网络概念 先有计算机还是先有网络呢&#xff1f; 答案是先有计算机&#xff0c;为了数据研究和沟通的需求产生的网络&#xff0c;网络的产生是为了提升效率的。 那什么是网络呢&#xff1f; 网络指的是网络协…

实现一个自定义的vue脚手架

开发背景 博客很久没有更新了&#xff0c; 今天更新一个好玩的&#xff0c;等我将vue3的东西彻底搞明白我会更新一个vue3的系列&#xff0c;到时候会更新稍微勤一点&#xff0c;在使用vuecli的时候发现他的脚手架很有意思&#xff0c;用了几年了&#xff0c;但是一直没有好好研…

HTML CSS 网页设计作业「动漫小站」

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 文章目录一、网页介绍一…

Neon intrinsics 简明教程

文章目录前言SIMD & NEONNEON intrinsicsNEON intrinsics 学习资料寄存器向量数据类型NENO intrinsics 命名方式NEON Intrinsics 查询三种处理方式&#xff1a;Long/Wide/NarrowNENO intrinsics 手册Addition 向量加法Vector add: vadd{q}_type. Vr[i]:Va[i]Vb[i]Vector lo…

Python-Flask 模型介绍和配置(6)

Flask数据模型和连接数据库一、安装二、配置数据库连接、创建模型类三、使用命令创建数据库表四、以注册为例flask是基于MTV的结构&#xff0c;其中M指的就是模型&#xff0c;即数据模型&#xff0c;在项目中对应的是数据库。flask与数据库建立联系有很多方法&#xff0c;但一般…

《安富莱嵌入式周报》第292期:树莓派单片机100M双通道示波器开源,MDK5.38发布,万用表单芯片解决方案,8通道±25V模拟前端芯片,开源贴片拾取电机板

往期周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 更新视频教程&#xff1a; GUI综合实战视频教程第3期&#xff1a;GUIX Studio一条龙设计主界面&#xff0c;底栏和…

【计算机毕业设计】32.学生宿舍管理系统源码

一、系统截图&#xff08;需要演示视频可以私聊&#xff09; 摘 要 随着计算机技术的飞速发展及其在宿舍管理方面应用的普及&#xff0c;利用计算机实现对学生宿舍管理势在必行。经过实际的需求分析&#xff0c;本系统采用Eclipse作为开发工具&#xff0c;采用功能强大的MySQL…

计算狗携手成都超算中心和重庆大学,共同助力“碳中和”

为了积极稳妥推进碳达峰碳中和&#xff0c;加快成渝双城经济圈建设。成都计算狗牵手国家超算中心和重庆大学&#xff0c;开展了关于二氧化碳电催化还原反应的路径计算工作&#xff0c;积极推动川渝两地实现产学研合作和成果落地转化&#xff0c;深入推进能源革命。 电催化还原二…

APS生产排单软件模拟排程功能

APS生产排单软件通过预先设定好相关基本资料与约束规则&#xff0c;当订单、机台、工具、材料、上下班时间等任何影响生产计划的因素变化后&#xff0c;执行“一键式排程计算”&#xff0c;系统即可生成生产详细排程。 通过选择不同的排产方案&#xff0c;可以实现不同的排程效…

3.60 怎么对OrCAD的网络标号进行统一批量修改?OrCAD中怎么设置复制位号的增加机制?

笔者电子信息专业硕士毕业&#xff0c;获得过多次电子设计大赛、大学生智能车、数学建模国奖&#xff0c;现就职于南京某半导体芯片公司&#xff0c;从事硬件研发&#xff0c;电路设计研究。对于学电子的小伙伴&#xff0c;深知入门的不易&#xff0c;特开次博客交流分享经验&a…

CANoe-vTESTstudio之Test Diagram编辑器(入门介绍)

1. 什么是Test Diagram编辑器 Test Diagram编辑器和Test Table编辑器不同 Test Table编辑器可以在编辑区域直接添加测试元素Test Case/Test Sequence/Test Fixture/Test Group,在CANoe软件的Test Unit里生成测试用例 Test Diagram编辑器以图形的方式定义实际的测试顺序、设…

springcloud16:总结配置中心+消息中心总结篇

架构图 启动分布式配置中心服务端从github中获取配置文件客户端访问服务端获取配置文件 当github中更改配置文件时&#xff0c;服务端可以立刻更改&#xff0c;但是客户端需要重启才能获取到更改的配置文件&#xff0c;如何优化&#xff1f; 即可以通过运维人员去手动刷新客户…

爬虫到底难在哪里?

爬虫本质是采集数据&#xff0c;通俗的讲就是模拟人在App或者浏览器的操作步骤自动化获取数据&#xff0c;本身没有什么难度&#xff0c;伪造HTTP 请求就好。 但是有些公司会给你设置采集障碍&#xff0c;大公司还有专门的安全团队防采集。 你看搞安全的程序员或者黑客平均技术…

【设计模式】组合模式(Composite Pattern)

组合模式属于结构型模式&#xff0c;又可以叫做部分-整体模式&#xff0c;主要解决客户程序在具有整体和部分的层次结构中&#xff0c;处理一组相似对象比处理单一对象费时费力的问题。例如&#xff0c;一个图形&#xff0c;它可以是一个简单的圆形、方形或一条线&#xff08;部…

paddleocr检测模型训练记录

标注好数据集后 分为训练集、测试集 数据集格式需要与配置文件一致&#xff0c;为了方便&#xff0c;我直接使用以下格式。 PaddleOCR主目录下&#xff0c;自己新建文件夹&#xff1a;car_plate_images/images_det train、test、里面是图片 det_label_test、det_label_train、…

Python遥感开发之GDAL读写遥感影像

Python遥感开发之GDAL读写遥感影像1 读取tif信息方法一2 读取tif信息方法二3 自己封装读取tif的方法&#xff08;推荐&#xff09;4 对读取的tif数据进行简单运算5 写出tif影像(推荐)前言&#xff1a;主要介绍了使用GDAL读写遥感影像数据的操作&#xff0c;包括读取行、列、投影…

基于51单片机霍尔汽车自行车码表测速测里程显示proteus仿真原理图PCB

功能&#xff1a; 0.本系统采用STC89C52作为单片机 1.LCD1602液晶分三种显示模式 a)显示实时速度和本次里程 b)显示当前时间 c)显示报警速度和总里程 2.超过报警速度将声光报警 3.功能按键介绍 a显示状态下: 上’键——电机速度1 下’键——电机速度-1 设置’键——电机启动/暂…

四、【基础】组件实例三大核心属性之一 state

文章目录1、CODE2、Result2.1、初始化2.2、触发更新3、state简写理解&#xff1a; state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件) 注意&#xff1a; 组件中rende…

算法导论习题—摊还时间代价分析、栈实现队列、贪心算法近似比、集合覆盖问题

在执行的nnn个操作中&#xff0c;有至多⌈lgn⌉⌈lg n⌉⌈lgn⌉个操作的次序是222的幂&#xff0c;这些操作的次序&#xff08;即代价&#xff09;如下 1,2,4,8,⋅⋅⋅,2⌈lgn⌉1, 2, 4, 8, , 2 ⌈lg n⌉ 1,2,4,8,⋅⋅⋅,2⌈lgn⌉ nnn个操作的总代价为 T∑k0⌈lgn⌉2k(n−⌈…

Android App网络通信中利用okhttp实现下拉刷新和上拉加载实战(抓取文章信息 超详细 附源码)

需要源码和工具类请点赞关注收藏后评论区留言私信~~~ 一、实现下拉刷新和上拉加载功能 网络上的信息很多&#xff0c;往往无法依次拉下来&#xff0c;故而App引入了分页加载功能&#xff0c;最开始先展示第一页内容&#xff0c;等到用户拉到该页底部后再去加载下一页内容&…