【整理】旅行商问题(traveling salesman problem,TSP)

news2025/1/15 20:33:46

旅行商

一个旅行商由某市出发,经过所有给定的n个城市后,再

回到出发的城市。除了出发的城市外,其它城市只经过一

回。这样的回路可能有多个,求其中路径成本最小的回路。

蛮力【穷举】

【例4-4】旅行商问题——排列树

 计算模型

(1) 存储 图G(V, E)。以邻接矩阵的方式存储,设计如下:

 

 (2)计算 设起始点下标为0

  生成排列树。设解空间为a,则其解空间的计算过程可描述为:

  求回路代价。设sumi 是并入第i个结点的代价 :

 sumi并入第i个结点的代价= sum_i-1代入第i-1个结点的代价 + 边(i-1到i)

分支限界法:

穷举法代码:

#include<iostream>
using namespace std;

//蛮力(穷举)
//邻接矩阵
int n = 4;
int edge[4][4] = { 0,8,5,4,
				8,0,7,3,
				5,7,0,1,
				4,3,1,0 };
int vertex[4] = { 'a','b','c','d' };
int a[] = { 0,1,2,3 };//解空间
int minvalue = 10000;//当前最小值
int path[4] = { 0 };//当前解的路径
int minpath[4] = { 0 };//最优解的路径

void showpath(int a[])
{
	cout << "showpath:";
	for (int i = 0; i < n; i++)
	{
		cout << a[i] << "\t";
	}
	//回到终点
	cout <<a[0]<< endl;
}
void copypath(int minpath[])
{
	for (int i = 0; i < n; ++i)
	{
		minpath[i] = a[i];
	}
}
int cost()//求当前路径的权值
{
	int sum = edge[0][a[0]];
	int i;
	for (i = 1; i < n; ++i)
	{
		sum += edge[a[i - 1]][a[i]];
	}
	sum += edge[a[i - 1]][0];//回到0
	return sum;
}
//递归
void EnumTSP(int i)
{
	int k, temp;
	if (i == n - 1)//最后一个结点,递归出口
	{
		if (cost() < minvalue)//当前解的权值 < 当前最小
		{
			minvalue = cost();//更新最小权值
			copypath(minpath);//赋值到最优解路径
		}
	}
	else
	{
		for (int k = i; k < n; k++)
		{
			//全排列
			temp = a[i]; a[i] = a[k]; a[k] = temp;
			//下一层递归
			EnumTSP(i + 1);
			//恢复现场
			temp = a[i]; a[i] = a[k]; a[k] = temp;
		}
	}
}

int	main()
{
	EnumTSP(0);//从第0层开始
	showpath(minpath);
	cout <<"minvalue:" << minvalue << endl;
	return 0;
}

回溯法代码:

#include<iostream>
#include<queue>
using namespace std;

//分支限界法
typedef struct node {
    bool vis[20];
    int st;//起点
    int ed;//终点
    int k;//走过的点数
    int sumv;//走过的路径距离
    int lb;//目标函数的值 下界
    int path[20];//当前路径
    //运算符重载
    bool operator<(const node& p)
    {
        return lb > p.lb;
    }
}node;

const int INF = 10000000; //表示无穷大
int low, up, n, used[20];
int cost[20][20] = { {4,2,6,9},{4,7,8,10}, {2,7,5,3}, {6,8,5,4}, {9,10,3,4} };
char city[20] = {'A','B','C','D','E'};
priority_queue<node>q;//优先级队列
node answer;//答案 最优解

//求上界
// 当前结点下标,第几层,当前路径长度
int get_up_digui(int v, int j, int len)
{
    int minE = INF;
    int pos;
    if (j == n)//结束
    {
        return len + cost[v][1];
    }
    //v的邻接边中最短
    for (int i = 1; i <= n; i++)//从1开始
    {
        //未访问
        if (used[i] == 0 && minE > cost[v][i])
        {
            minE = cost[v][i];
            pos = i;
        }
    }
    //设为访问
    used[pos] = 1;
    return get_up_digui(pos, j + 1, len + minE);
}
//求上界
void get_up()//求当前上界
{
    used[1] = 1;//从1开始访问
    up = get_up_digui(1, 1, 0);
}
//下界
void get_low()//求当前下界
{
    low = 0;
    //最短两条边
    for (int i = 1; i <= n; i++)
    {
        int temp[20];
        for (int j = 1; j <= n; j++)
        {
            temp[j] = cost[i][j];
        }
        sort(temp + 1, temp + 1 + n);//从1开始
        low += temp[1]+temp[2];
    }
    low = low / 2;
    //其他顶点最短两条边

}

//结点所在分支的下界
int get_lb(node p)
{
    int res = p.sumv * 2;//已遍历的城市的距离
    int min1 = INF, min2 = INF, pos;
    //从起点 到 最近未遍历的城市的距离
    for (int i = 1; i <= n; i++)
    {
        if (p.vis[i] == 0 && min1 > cost[p.st][i])
        {
            min1 = cost[p.st][i];
            pos = i;
        }
    }
    res += min1;
    //从离开结点 到最近未便利城市的距离
    for (int i = 1; i <= n; i++)
    {
        if (p.vis[i] == 0 && min2 > cost[i][p.ed])
        {
            min2 = cost[i][p.ed];
            pos = i;
        }
    }
    res += min2;
    //进入并离开 每个未遍历城市的最小成本
    for (int i = 1; i <= n; i++)
    {
        if (p.vis[i] == 0)
        {
            int temp[n];
            min1 = min2 = INF;
            for (int j = 1; j <= n; j++)
            {
                temp[j] = cost[i][j];
            }
            sort(temp + 1, temp + 1 + n);
            res += temp[1] + temp[2];
        }
    }
    //向上取整
    res = res % 2 == 0 ? (res / 2 ):( res / 2 + 1);
    return res;
}

//求解
int solve()
{
    int res = INF;//
    get_up();//求当前上界
    get_low();//当前下界
    node s;//
    s.st = 1;//起始节点
    s.ed = 1;//终点
    s.k = 1;//走过点数
    s.sumv = 0;//
    s.lb = low;//当前下界
    s.path[0] = 0;//起点
    //
    for (int i = 0; i < n; i++)//初始化 未访问,没有路径
    {
        s.vis[i] = 0;
        s.path[i] = 0;
    }
    s.vis[0] = 1;//访问第一个结点
    q.push(s);//当前结点入队
    node next, temp;
    while (!q.empty())
    {
        temp = q.top();//队首元素
        q.pop();//出队
        //结束条件
        //只剩最后一个点
        if (temp.k == n - 1)
        {
            int pos = 0;
            for (int i = 1; i <= n; i++)//从0开始
            {
                if (temp.vis[i] == 0)//如果路径上第i点没有被访问
                {
                    pos = i;
                    break;
                }
            }
            //结束遍历
            if (pos == 0)//都被访问
            {
                break;//结束while循环
            }
            //还有pos没有访问到,那么设最后一个结点是POS
            //结果 当前路径长度+当前路径终点到POS+POS到当前路径起点
            int ans = temp.sumv+cost[pos][temp.st]+cost[temp.ed][pos];
            //最后一个结点是POS 
            temp.path[n] = pos;//从结点1开始计算
            //当前路径的上界是
            temp.lb = ans;
            //此时队首元素,也是当前最优元素
            node judge = q.top();
            //如果当前路径比所有分支的上界都小,找到最优解
            if (ans <= judge.lb);
            {
                res = min(ans, res);
                answer = temp;//返回答案
                break;
            }
            else if(up>=ans)//当前答案<=上界,还是有可能从其他路径更新上界
            {
                up = ans;
                answer = temp;

            }
            res = min(res, ans);//更新此时的最小值
            continue;

        }
        //
        for (int i = 1; i <= n; i++)
        {
            if (temp.vis[i] == 0)//当前路径还未访问该点
            {
                next.st = temp.st;//一样的起点
                next.ed = i;//结点是i
                next.k = temp.k + 1;//点数+1
                next.sumv = temp.sumv + cost[temp.ed][i];//更新
                for (int j = 1; j <= n; j++)
                {
                    next.vis[j] = temp.vis[j];
                    next.path[j] = temp.path[j];
                }
                next.vis[i] = 1;
                next.path[next.k] = 1;//路径
                next.lb = get_lb(next);//下界 
                if (next.lb <= up)//如果
                {
                    q.push(next);
                }//反之,剪枝
            }
        }
    }
    return res;
}

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

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

相关文章

CSS3选择器详解 前端开发入门笔记(六)

CSS3选择器是一种用于定位HTML元素的方式&#xff0c;它们可以使样式表更加精确地应用到特定的元素。下面是一些常用的CSS3选择器&#xff1a; 元素选择器&#xff08;Element Selector&#xff09;&#xff1a;使用元素名称作为选择器&#xff0c;匹配对应名称的所有元素。例如…

esp32 arduino使用多个串口时如何查看serial1,serial2所对应的引脚定义

如上图所示可以通HardwareSerial.cpp找到起对应的引脚去连接设备即可

5.12.webrtc接口调用过程

嗨&#xff0c;大家好&#xff0c;我是李超&#xff0c;在上节课中呢&#xff0c;我向你介绍了外接口的设计以及我们红接口展开之后的样子&#xff0c;对吧&#xff1f;那今天呢&#xff1f;我们再来看看整个接口调用过程。那整个这个调用过程啊&#xff0c;非常的复杂&#xf…

域控操作二:设置域用户使用简单密码

过程太多简单 直接写出路径更改即可 组策略—计算机配置----策略—Windows设置–安全设置----账户策略–密码策略 按自己想法改就行了 注意一点&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 要么自己设置策略&#xff0c;要么从默认策略改&#xff01;&am…

数学分析:傅里叶级数

卓里奇书好的一点就是&#xff0c;不是直接引出公式&#xff0c;而是告诉你理由。先引出正交的概念&#xff0c;然后在函数空间中&#xff0c;也有正交&#xff0c;只不过是无限维的空间。 这里要注意&#xff0c;明确说明了是有限个。 在函数空间里面&#xff0c;内积是指进行…

CUDA编程入门系列(九)CUDA程序执行与硬件映射

一、GPU流式多处理器 1.kernel的线程组织层次&#xff0c;一个kernel实际上会启动很多线程&#xff0c;这些线程时逻辑上并行的&#xff0c;但是在物理层上不一定是并行的。 2.GPU硬件的一个核心组件时SM&#xff0c;streaming multiprocessor 流式多处理器 3.SM的核心组件包括…

数字电子技术——半导体存储电路

一、半导体存储电路概述 1.存储电路及其分类 存储单元&#xff1a;一位数据 寄存器&#xff1a;一组数据 存储器&#xff1a;大量数据 2.存储单元分类 1&#xff09;静态存储单元 门电路连接而成 2&#xff09;动态存储单元 电容电荷积累 3.存储单元的触发方式与逻辑…

SSH连接华为交换机慢

ssh连接交换机慢是因为交换计算密钥算法阶段默认使用安全性更高的秘钥&#xff0c;由于性能问题导致连接比较慢&#xff0c;如一台华为S5735S-L24T4S-QA2的交换机默认使用如下秘钥&#xff0c;安全行由高到低。 ssh server key-exchange dh_group16_sha512 dh_group15_sha512 …

firewalld常用的基础配置

firewalld防火墙是centos7系统默认的防火墙管理工具&#xff0c;取代了之前的iptables防火墙&#xff0c;也是工作在网络层&#xff0c;属于包过滤防火墙。 支持IPv4、IPv6防火墙设置以及以太网桥支持服务或应用程序直接添加防火墙规则接口拥有两种配置模式&#xff1a;临时模…

C#桶排序算法

前言 桶排序是一种线性时间复杂度的排序算法&#xff0c;它将待排序的数据分到有限数量的桶中&#xff0c;每个桶再进行单独排序&#xff0c;最后将所有桶中的数据按顺序依次取出&#xff0c;即可得到排序结果。 实现原理 首先根据待排序数据&#xff0c;确定需要的桶的数量。…

网工记背命令(6)----链路聚合配置

目录 1.配置手工负载分担模式链路聚合 2.配置LACP模式的链路聚合 3.HUAWEI设备与C厂商设备对接 链路聚合&#xff08;Link Aggregation&#xff09;是将多条物理链路捆绑在一起成为一条逻辑链路&#xff0c;从而增加链路带 宽的技术。 常用配置命令 1、执行命令 interface …

轻重链剖分+启发式合并专题

Codeforces-741D(Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths) 一棵根为1 的树&#xff0c;每条边上有一个字符&#xff08;a-v共22种&#xff09;。 一条简单路径被称为Dokhtar-kosh当且仅当路径上的字符经过重新排序后可以变成一个回文串。 求每个子树中…

第三章 内存管理 九、基本分段存储管理方式

目录 一、概括 二、什么是分段 三、段表 四、地址转换 五、分段和分页的对比 六、总结 一、概括 基本分段存储管理方式是一种操作系统的内存管理方式&#xff0c;采用这种方式&#xff0c;将进程所需的内存分成若干个段&#xff0c;每个段都可以单独进行管理和保护。 具…

分享一下怎么开发一个陪诊小程序

开发一个陪诊小程序需要综合考虑许多方面&#xff0c;包括但不限于市场需求、用户体验、技术实现和运营策略。以下是一篇以开发陪诊小程序为主题的文章。 一、背景介绍 随着社会的发展和人口老龄化的加剧&#xff0c;越来越多的老年人、病患和孕妇需要就医&#xff0c;而由于各…

攻防世界web篇-unserialize3

得出php代码残篇 将代码补全后再在线php运行工具中进行运行 在浏览器输入后得到下面的界面 这里需要将O:4:“xctf”:1:{s:4:“flag”;s:3:“111”;} 改为 O:4:“xctf”:2:{s:4:“flag”;s:3:“111”;}

【Leetcode】212.单词搜索II(Hard)

一、题目 1、题目描述 给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。 单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中…

【java】Java项目从开发到部署生产完整流程梳理

文章目录 前言一、开发环境二、项目搭建2.1 Maven创建项目2.1.1 创建maven项目2.1.2 引入依赖2.1.3 maven常用命令 三、SpringBoot基础配置四、项目打包4.1 打包jar4.2 打包war4.2.1 修改项目打包为war包4.2.2 排除内嵌的tomcat&#xff0c;引入外部tomcat4.2.3 添加servlet-ap…

Unity可视化Shader工具ASE介绍——8、UI类型的特效Shader编写

阿赵的Unity可视化Shader工具ASE介绍目录 Unity的UGUI图片特效角色闪卡效果 大家好&#xff0c;我是阿赵。   继续介绍Unity可视化Shader编辑插件ASE的使用。这次讲一下UI类特效Shader的写法。 一、例子说明 这次编写一个Shader&#xff0c;给一张UGUI里面的图片增加一个闪卡…

攻防世界web篇-Training-WWW-Robots

直接点击给出的地址&#xff0c;然后会转到另一个网页界面&#xff0c;在这个界面&#xff0c;已经给出了提示&#xff0c;robots.txt 在浏览器中&#xff0c;直接在地址的后面加上robots.txt&#xff0c;会进到下面这个界面 因为对php语言一窍不通&#xff0c;所以这里纯粹就…

Swagger有哪些非常重要的注释?

Swagger是一种用于描述和定义RESTful API的强大工具&#xff0c;它提供了一种规范来编写API文档&#xff0c;生成客户端SDK以及进行自动化测试。其中的注释&#xff08;Annotations&#xff09;在Swagger规范中扮演着关键的角色&#xff0c;用于为API端点、操作、模型等添加元数…