【算法设计与分析】基于Go语言实现动态规划法解决TSP问题

 本文针对于最近正在学习的Go语言,以及算法课实验所需内容进行Coding,一举两得!     

一、前言 

        由于这个实验不要求向之前的实验一样做到那种连线的可视化,故可以用图形界面不那么好实现的语言进行编写,考虑到Go语言的方兴未艾,所以采用此种语言解决问题。


 二、问题

        TSP问题的大致解法,老师在课上已经说过了,清华大学出版社的《算法设计与分析》(第二版,然而书上伪代码存在一些疏漏)里面也有所阐述,这里不做细致解释。


三、代码分析

        主要可分为三个部分,输入、输出、计算。

1.输入

        输入部分需要一个整型变量存点(城市)的数量,一个矩阵存点到点的距离,另外增设一个矩阵存到某个点的最近的前驱。这里还有一个重要的问题是如何做出这些点的子集,也就是所要画的图(实验结果)的表头横向内容,代码如下:

 ·········var nums int// 读取二维数组的行数和列数fmt.Print("请输入(城市)点数: ")fmt.Scanln(&nums)// 初始化一个二维数组arc := make([][]int, nums)for i := range arc {arc[i] = make([]int, nums)}// 从控制台读取二维数组的值fmt.Println("请输入二维数组的元素,每行输入完毕后按回车键:")for i := 0; i < nums; i++ {for j := 0; j < nums; j++ {var putin intfmt.Scanf("%d", &putin)if putin == 0 {putin = 2004}arc[i][j] = putin}fmt.Scanln() // 跳过每行输入后的换行符}var LengthOfd int = int(math.Pow(2, float64(nums-1)))//下面的这个d就是那个动态规划法的表d := make([][]int, nums)for i := range d {d[i] = make([]int, LengthOfd)}//同样设置一个跟d一样的矩阵,来存最近的前驱front := make([][]int, nums)for i := range front {front[i] = make([]int, LengthOfd)}//初始化设置成很大的for i := range d {for j := range d[i] {d[i][j] = 2004front[i][j] = 2004}}for i := 0; i < nums; i++ {d[i][0] = arc[i][0]front[i][0] = 0}// 创建一维存所有要做子集的点。numName := make([]int, nums) //生成1,2,3for i := 1; i <= nums; i++ {numName[i-1] = i}numName = numName[:len(numName)-1]subset := subsets(numName) //生成子集sort.Slice(subset, func(i, j int) bool {return len(subset[i]) < len(subset[j])})fmt.Println(subset)
·······

        这里前面几个变量我不加以赘述,简单的创建和初始化(如果你用的其他语言写这道题,相信你能做到这个),这里说一下最小子集的寻找,我参考了LeetCode上一道题(力扣上的子集问题)以及CSDN上相关博主给出的解答(子集问题的GO语言其中一解,这里选择解题代码并未考虑时间及空间复杂度,大家可以试着采用leetcode上更快更好的代码),代码大意是采用了深度优先搜索的方式进行生成,下面是本题借用函数:

//来自于本站其他用户博文
func subsets(nums []int) [][]int {l := list.New()result := list.New()for i := 0; i <= len(nums); i++ {dfs(nums, 0, i, l, result)}arr := make([][]int, result.Len())k := 0for e := result.Front(); e != nil; e = e.Next(){curl := e.Value.(*list.List)arr[k] = make([]int, curl.Len())k++}i := 0;for e := result.Front(); e != nil; e = e.Next() {curl := e.Value.(*list.List)j := 0for p := curl.Front(); p != nil; p = p.Next() {arr[i][j] = p.Value.(int)j++}i++;}return arr
}func dfs(nums []int, start int, len int, l *list.List, result *list.List) {if start == len {a := list.New()for e := l.Front(); e != nil; e = e.Next() {a.PushBack(e.Value)}result.PushBack(a)return}for i := start; i < len; i++ {l.PushBack(nums[i])dfs(nums, i+1, len, l, result)b := l.Back()l.Remove(b)}
}

         之后就是对得到的子集排序,因为上面代码跑出来的子集并非是有序的。

        这样便得到了计算所需的所有变量。

2.计算 

        计算这个方面可以参考书中的伪代码,值得注意的是在判断大小的一些地方需要改变传参。大家可通过书中样例矩阵以及表格进行相关推导,不难发现第0列被初始化,第1到3列依靠0列更新,3到5列依靠1到3列更新,第七列特别只有第0行需要更新,而我们的最终结果需要的表格的横向表头的二维数组长这样:

        所以,我们不难发现,它的长度似乎和计算有一些联系:在遍历{1}的时候,填充2和3,依靠0到1的数值,所以计算d[2][1] = arc[2][1] + d[1][0],d[3][1] = arc[3][1] + d[1][0]。依照此种规律,便可结合下文的代码内容分析:

	//下面才刚刚开始tspfor j := 1; j < len(subset); j++ { //以d中的列开始扫描,也就是上面的subset(V),表头内容for i := 1; i < nums; i++ { //挨个查找看看那个数字没在V的里面,真没在就去赋值,正在查找横表头有无judge := falsefor _, theNum := range subset[j] {if theNum == i {judge = true //在的在的,就不用操作了continue}}if judge == false { //wok,真没在var edge int = 1314for _, theNum := range subset[j] { //theNum,表头中含有的内容temp := arc[i][theNum] + d[theNum][j-theNum-len(subset[j])+1]if temp < edge {edge = tempd[i][j] = edgefront[i][j] = theNum}//d[i][j] = int(math.Min(float64(d[i][j]), float64(arc[i][theNum]+d[theNum][j-1])))}}}}//求解最终结果TheLastEdge := 520for k := 1; k < nums; k++ {temp := arc[0][k] + d[k][LengthOfd-1-int(math.Pow(2, float64(k-1)))]if temp < TheLastEdge {TheLastEdge = tempd[0][LengthOfd-1] = TheLastEdgefront[0][LengthOfd-1] = k}}

         最后求解最后的那个框,例如本题目就是{1,2,3}下面的第0行的内容。和上面写到的,平常的点求该问题的解法如出一辙,这里并不是很好理解,计算公式甚至可以理解成是我在纯粹的找规律凑数。值得注意的是,在计算的同时front矩阵也在记录他们的上一个点(前驱),这个在后面输出发挥着重要作用。

3.输出

        这里要注意以下路径是怎么打印出来的。i是直接指向最后更新的那个框框的纵坐标,j是横坐标,count用来计数确保不会在移动的途中迷路,根据书上表格,我们并不难发现下面的规律,j直接取front中的值会直接得到前驱节点的值,那么我们就应该在j行去定位这个前驱的下一个前驱,那么我们就只差寻找这个地方的纵坐标,相当巧合的是,现在的i减去现在的j的值再减去这次遍历计数器的值,正好到了我们要寻找的那个地方。(这里在草稿纸上列出我们需要加起来的点,恰好可以得出普适的规律)。

	fmt.Print("TSP最短的路径是:", "0")//打印路径for i, j, count := LengthOfd-1, 0, 0; ; {j = front[j][i]i = i - j - countfmt.Print("->", j)count++if i <= 0 {fmt.Println("->0")break}}//画表for i := 0; i < len(subset); i++ {str := fmt.Sprint(subset[i])fmt.Printf("%10s", str)//fmt.Print(subset[i], "\t\t")}fmt.Println()for i := 0; i < nums; i++ {for j := 0; j < LengthOfd; j++ {bye := "-"if d[i][j] == 2004 {fmt.Printf("%10s", bye)} else {fmt.Printf("%10d", d[i][j])}}fmt.Println()}fmt.Println("最短路径是:", d[0][LengthOfd-1])

        大致就是这样得到了最后结果,然后再对齐一下表格。


四、代码

// TSP Problem
// Created By DDD on 2024.5.25
//package mainimport ("container/list""fmt""math""sort"
)func subsets(nums []int) [][]int {l := list.New()result := list.New()for i := 0; i <= len(nums); i++ {dfs(nums, 0, i, l, result)}arr := make([][]int, result.Len())k := 0for e := result.Front(); e != nil; e = e.Next() {curl := e.Value.(*list.List)arr[k] = make([]int, curl.Len())k++}i := 0for e := result.Front(); e != nil; e = e.Next() {curl := e.Value.(*list.List)j := 0for p := curl.Front(); p != nil; p = p.Next() {arr[i][j] = p.Value.(int)j++}i++}return arr
}func dfs(nums []int, start int, len int, l *list.List, result *list.List) {if start == len {a := list.New()for e := l.Front(); e != nil; e = e.Next() {a.PushBack(e.Value)}result.PushBack(a)return}for i := start; i < len; i++ {l.PushBack(nums[i])dfs(nums, i+1, len, l, result)b := l.Back()l.Remove(b)}
}func main() {var nums int// 读取二维数组的行数和列数fmt.Print("请输入(城市)点数: ")fmt.Scanln(&nums)// 初始化一个二维数组arc := make([][]int, nums)for i := range arc {arc[i] = make([]int, nums)}// 从控制台读取二维数组的值fmt.Println("请输入二维数组的元素,每行输入完毕后按回车键:")for i := 0; i < nums; i++ {for j := 0; j < nums; j++ {var putin intfmt.Scanf("%d", &putin)if putin == 0 {putin = 2004}arc[i][j] = putin}fmt.Scanln() // 跳过每行输入后的换行符}var LengthOfd int = int(math.Pow(2, float64(nums-1)))//下面的这个d就是那个动态规划法的表d := make([][]int, nums)for i := range d {d[i] = make([]int, LengthOfd)}//同样设置一个跟d一样的矩阵,来存最近的前驱front := make([][]int, nums)for i := range front {front[i] = make([]int, LengthOfd)}//初始化设置成很大的for i := range d {for j := range d[i] {d[i][j] = 2004front[i][j] = 2004}}for i := 0; i < nums; i++ {d[i][0] = arc[i][0]front[i][0] = 0}// 创建一维存所有要做子集的点。numName := make([]int, nums) //生成1,2,3for i := 1; i <= nums; i++ {numName[i-1] = i}numName = numName[:len(numName)-1]subset := subsets(numName) //生成子集sort.Slice(subset, func(i, j int) bool {return len(subset[i]) < len(subset[j])})fmt.Println(subset)//下面才刚刚开始tspfor j := 1; j < len(subset); j++ { //以d中的列开始扫描,也就是上面的subset(V),表头内容for i := 1; i < nums; i++ { //挨个查找看看那个数字没在V的里面,真没在就去赋值,正在查找横表头有无judge := falsefor _, theNum := range subset[j] {if theNum == i {judge = true //在的在的,就不用操作了continue}}if judge == false { //wok,真没在var edge int = 1314for _, theNum := range subset[j] { //theNum,表头中含有的内容temp := arc[i][theNum] + d[theNum][j-theNum-len(subset[j])+1]if temp < edge {edge = tempd[i][j] = edgefront[i][j] = theNum}//d[i][j] = int(math.Min(float64(d[i][j]), float64(arc[i][theNum]+d[theNum][j-1])))}}}}//求解最终结果TheLastEdge := 520for k := 1; k < nums; k++ {temp := arc[0][k] + d[k][LengthOfd-1-int(math.Pow(2, float64(k-1)))]if temp < TheLastEdge {TheLastEdge = tempd[0][LengthOfd-1] = TheLastEdgefront[0][LengthOfd-1] = k}}fmt.Print("TSP最短的路径是:", "0")//打印路径for i, j, count := LengthOfd-1, 0, 0; ; {j = front[j][i]i = i - j - countfmt.Print("->", j)count++if i <= 0 {fmt.Println("->0")break}}//画表for i := 0; i < len(subset); i++ {str := fmt.Sprint(subset[i])fmt.Printf("%10s", str)//fmt.Print(subset[i], "\t\t")}fmt.Println()for i := 0; i < nums; i++ {for j := 0; j < LengthOfd; j++ {bye := "-"if d[i][j] == 2004 {fmt.Printf("%10s", bye)} else {fmt.Printf("%10d", d[i][j])}}fmt.Println()}fmt.Println("最短路径是:", d[0][LengthOfd-1])
}/*
4
0 3 6 7
5 0 2 3
6 4 0 2
3 7 5 0
*/

五、参考文献

 算法分析与设计课程实验——TSP问题与01背包问题的动态规划算法实现-CSDN博客


Go的主函数不需要写return

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

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

相关文章

C# 结合 JS 暴改腾讯 IM SDK Demo

目录 关于腾讯 IM SDK Demo 范例运行环境 设计思路 服务端生成地址 IM 服务端接收 IM 客户端程序 小结 关于腾讯 IM SDK Demo 腾讯云即时通信 IM SDK 提供了单聊、群聊、关系链、消息漫游、群组管理、资料管理、直播弹幕等功能&#xff0c;并提供完备的 App 接入及管…

数据可视化第9天(利用wordcloud和jieba分析蝙蝠侠评论的关键字)

数据可以在这里下载 https://github.com/harkbox/DataAnalyseStudy WordCloud wordcloud可以很方便的生成词云图&#xff0c;方便的提供可视化可以直接使用pip install wordcloud进行安装如果使用的是Anaconda,可以使用conda install进行安装 下面看一个简单的例子 txt &qu…

【游戏引擎】Unity动画系统详解

持续更新。。。。。。。。。。。。。。。 【游戏引擎】Unity动画系统详解 Unity动画系统详解简介关键帧动画创建关键帧动画的步骤&#xff1a; Mecanim动画系统Mecanim的关键组件&#xff1a;使用Mecanim创建动画的步骤&#xff1a; 动画控制器动画控制器的高级功能&#xff1a…

【STM32CubeIDE】软件硬件SPI+六针OLED使用

前言 本文将介绍STM32 6针OLED的使用&#xff0c;分别使用软件和硬件两种SPI驱动方式&#xff0c;最终实现OLED显示TEST-ok字符和数字累加刷新显示 软件平台&#xff1a;STM32CubeIDEHAL库 硬件&#xff1a;STM32F103ZET6(正点原子战舰V3)六针OLED 题外话&#xff1a; 最…

Commons-Collections篇-CC1链小白基础分析学习

1.介绍 Apache Commons工具包中有⼀个组件叫做 Apache Commons Collections &#xff0c;其封装了Java 的 Collection(集合) 相关类对象&#xff0c;它提供了很多强有⼒的数据结构类型并且实现了各种集合工具类&#xff0c;Commons Collections被⼴泛应⽤于各种Java应⽤的开发&…

Windows安装VMware(Broadcom)

1.安装前提 1.检查BIOS中是否开启了虚拟化技术。1.1 打开任务管理器&#xff0c;查看性能&#xff0c;CPU部分&#xff0c;虚拟化处于“已启用”状态。1.2 如果没有开启&#xff0c;则需要进入BIOS系统&#xff0c;将 Intel Virtualization Technology改为Enalble。2.下载VMwa…

卷积神经网络CNN动态演示和输出特征图计算公式

目录 一、卷积运算 1、卷积&#xff08;Convolution&#xff09; 2、填充&#xff08;Padding&#xff09; &#xff08;1&#xff09;Valid Padding &#xff08;2&#xff09;Same Padding 3、步长 4、卷积核大小为什么一般为奇数奇数&#xff1f; 5、卷积核kernel和…

笔记88:LeetCode_134_加油站

前言&#xff1a; 前言1&#xff1a;这个题的题目条件给的不太严谨&#xff0c;题目描述中说“如果存在解&#xff0c;则保证它是唯一的”&#xff0c;通过我的实践&#xff0c;我发现这句话的意思其实是本题的所有样例只有两种情况&#xff0c;无解/有唯一解&#xff1b;而不可…

迅睿 CMS 中开启【ionCube 扩展】的方法

有时候我们想要某种功能时会到迅睿 CMS 插件市场中找现有的插件&#xff0c;但会有些担心插件是否适合自己的需求。于是迅睿 CMS 考虑到这一层推出了【申请试用】&#xff0c;可以让用户申请试用 30 天&#xff0c;不过试用是有条件的&#xff0c;条件如下&#xff1a; php 版…

Midjourney是一个基于GPT-3.5系列接口开发的免费AI机器人

Midjourney是一个基于GPT-3.5系列接口开发的免费AI机器人&#xff0c;旨在提供多领域的智能对话服务。Midjourney在不同领域中有不同的定义和应用&#xff0c;以下是对其中两个主要领域的介绍&#xff1a; Midjourney官网&#xff1a;https://www.midjourney.com/ 一、AI绘画工…

Windows11搭建Flutter3开发环境

下载&#xff1a;https://docs.flutter.cn/get-started/install/windows/desktop?tabdownload 下载以后解压到C盘&#xff1a; 将bin目录添加到环境变量PATH&#xff1a; 打开终端&#xff0c;输入&#xff1a; flutter doctor执行下面的命令&#xff0c;同意安卓协议&am…

llama3-8b-instruct-262k微调过程的问题笔记(场景为llama论文审稿)

目录 一、环境配置 1.1、模型 1.2、微调环境 1.3、微调数据 二、发现的问题 2.1、过拟合问题 2.2、Qlora zero3 保存模型时OOM问题(已解决) 一、环境配置 1.1、模型 llama3-8b-instruct-262k &#xff08;英文&#xff09; 1.2、微调环境 Package Version ------------------…

开关电源AC-DC(15W 3-18V可调)

简介: 该模块使用PI的TNY268PN电源芯片制作的开关电源,实现最大功率15W 3-18V可调输出(更改反馈电阻)隔离式反激电源; 简介:该模块使用PI的TNY268PN电源芯片制作的开关电源,实现最大功率15W 3-18V可调输出(更改反馈电阻,现电路图输出5V)隔离式反激电源; 一、产品简…

【C++】详解AVL树——平衡二叉搜索树

个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 祝福语&#xff1a;愿你拥抱自由的风 目录 二叉搜索树 AVL树概述 平衡因子 旋转情况分类 左单旋 右单旋 左右双旋 右左双旋 AVL树节点设计 AVL树设计 详解单旋 左单旋 右单旋 详解双旋 左右双旋 平衡因子情况如…

ESP32 接入点灯科技实现远程控制(物联网)

文章目录 ESP32-C3MQTT协议blinker App 源码blinker 开发者Arduino 支持文档导入 blinker 库注册点灯 APPblinker WiFi 示例blinker 蓝牙示例 本示例中开发板使用的是Seeed Studio (XIAO-ESP32-C3) ESP32-C3 ESP32-C3 是 Espressif Systems 公司开发的一款单核 Wi-Fi 和蓝牙双模…

「云渲染课堂」3dmax地砖材质参数怎么让画面更加真实?

在3DMAX中&#xff0c;地砖材质的渲染需要细致的调整&#xff0c;因为不同材质的地砖在反射和折射参数上各不相同。为了使地砖材质更加逼真&#xff0c;以下简要说明了一些设置方法&#xff0c;希望对大家有所帮助&#xff01; 3dmax地砖材质参数如何设置 1、打开材质编辑器&a…

性能测试--线程的监控

1.线程的状态 1.1.线程的5种状态 java的线程总共有5种状态&#xff0c;如下&#xff1a; * 新建&#xff1a;new 【新建之后不启用都是new】* 运行&#xff1a;runnable* 等待&#xff1a;waitting(无限期等待),timed waitting(限期等待)* 阻塞&#xff1a;blocked* 结束&am…

LaTex 模板 - 东北师范大学申研申博推荐信

文章目录 NENU-Letter-Template项目地址示例特性项目结构如何使用main.texletterContent.tex 如何编译方式 1 &#xff1a;在线编译方式 2 &#xff1a;本地编译 参考 NENU-Letter-Template NENU’s recommendation letter template. 东北师范大学推荐信模板 项目地址 GitHu…

网络爬虫原理及其应用

你是否想知道Google 和 Bing 等搜索引擎如何收集搜索结果中显示的所有数据。这是因为搜索引擎对其档案中的所有页面建立索引&#xff0c;以便它们可以根据查询返回最相关的结果。网络爬虫使搜索引擎能够处理这个过程。 本文重点介绍了网络爬虫的重要方面、网络爬虫为何重要、其…

【学习笔记】Webpack5(Ⅱ)

Webpack 3、高级篇 3.1、提升开发体验 —— SourceMap 3.2、提升打包速度 3.2.1 HotModuleReplacement 3.2.2 OneOf 3.2.3 Include / Exclude 3.2.4 Cache 3.2.5 Thread 3.3、减少代码体积 …