【算法设计与分析】基于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 int
			fmt.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] = 2004
			front[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,3
	for 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 := 0
    for 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 := 0
        for 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]。依照此种规律,便可结合下文的代码内容分析:

	//下面才刚刚开始tsp
	for j := 1; j < len(subset); j++ { //以d中的列开始扫描,也就是上面的subset(V),表头内容
		for i := 1; i < nums; i++ { //挨个查找看看那个数字没在V的里面,真没在就去赋值,正在查找横表头有无
			judge := false
			for _, theNum := range subset[j] {
				if theNum == i {
					judge = true //在的在的,就不用操作了
					continue
				}
			}
			if judge == false { //wok,真没在

				var edge int = 1314

				for _, theNum := range subset[j] { //theNum,表头中含有的内容
					temp := arc[i][theNum] + d[theNum][j-theNum-len(subset[j])+1]
					if temp < edge {
						edge = temp
						d[i][j] = edge
						front[i][j] = theNum
					}
					//d[i][j] = int(math.Min(float64(d[i][j]), float64(arc[i][theNum]+d[theNum][j-1])))
				}
			}
		}
	}
	//求解最终结果
	TheLastEdge := 520
	for k := 1; k < nums; k++ {
		temp := arc[0][k] + d[k][LengthOfd-1-int(math.Pow(2, float64(k-1)))]
		if temp < TheLastEdge {
			TheLastEdge = temp
			d[0][LengthOfd-1] = TheLastEdge
			front[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 - count
		fmt.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 main

import (
	"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 := 0
	for 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 := 0
		for 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 int
			fmt.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] = 2004
			front[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,3
	for 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)
	//下面才刚刚开始tsp
	for j := 1; j < len(subset); j++ { //以d中的列开始扫描,也就是上面的subset(V),表头内容
		for i := 1; i < nums; i++ { //挨个查找看看那个数字没在V的里面,真没在就去赋值,正在查找横表头有无
			judge := false
			for _, theNum := range subset[j] {
				if theNum == i {
					judge = true //在的在的,就不用操作了
					continue
				}
			}
			if judge == false { //wok,真没在

				var edge int = 1314

				for _, theNum := range subset[j] { //theNum,表头中含有的内容
					temp := arc[i][theNum] + d[theNum][j-theNum-len(subset[j])+1]
					if temp < edge {
						edge = temp
						d[i][j] = edge
						front[i][j] = theNum
					}
					//d[i][j] = int(math.Min(float64(d[i][j]), float64(arc[i][theNum]+d[theNum][j-1])))
				}
			}
		}
	}
	//求解最终结果
	TheLastEdge := 520
	for k := 1; k < nums; k++ {
		temp := arc[0][k] + d[k][LengthOfd-1-int(math.Pow(2, float64(k-1)))]
		if temp < TheLastEdge {
			TheLastEdge = temp
			d[0][LengthOfd-1] = TheLastEdge
			front[0][LengthOfd-1] = k
		}
	}

	fmt.Print("TSP最短的路径是:", "0")
	//打印路径
	for i, j, count := LengthOfd-1, 0, 0; ; {
		j = front[j][i]
		i = i - j - count
		fmt.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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2、xss-labs之level2

1、打开页面 2、传入xss代码 payload&#xff1a;<script>alert(xss)</script>&#xff0c;发现返回<script>alert(xss)</script> 3、分析原因 打开f12&#xff0c;没什么发现 看后端源码&#xff0c;在这form表单通过get获取keyword的值赋给$str&am…

下雨!大水蚁引发的水文!看比赛咯,曼联VS曼城——早读(逆天打工人爬取热门微信文章解读)

唠唠嗑 水一水 引言Python 代码结尾 引言 今天星期六 大小周 一个等了很久的双休 昨天晚上真的是吓到我了 漫天的小飞虫 我一开始还以为是一两只 没想到那些小飞虫 从阳台不断飞进来 在山卡拉下面租房子 也是太恐怖了 来个特写 他们也就一个晚上的时间 成虫 天气合适 长翅…

使用docker commit创建新镜像

前言 我们知道&#xff0c;从docker-hub上拉取的镜像所创建的容器是最小版本的&#xff0c;比如ubuntu内部是没有vim编辑器的&#xff0c;我们需要自己手动安装&#xff0c;但是当我们安装后假如有人把我们的容器误删了&#xff0c;那么我们再次根据原始镜像创建的容器就没有了…

优先级队列(堆)的实现

1.什么是优先级队列 队列是一种先进先出(FIFO)的数据结构&#xff0c;但有些情况下&#xff0c;操作的数据可能带有优先级&#xff0c;一般出队 列时&#xff0c;可能需要优先级高的元素先出队列&#xff0c;该中场景下&#xff0c;使用队列显然不合适&#xff0c;比如&#x…

Drone+Gitee自动执行构建、测试和发布工作流

拉取Drone:(至于版本&#xff0c;你可以下载最新的) sudo docker pull drone/drone:2 拉取runner&#xff1a; sudo docker pull drone/drone-runner-docker 在Gitee中添加第三方应用&#xff1a; 进入个人主页&#xff0c;点击设置&#xff1a; 往下翻&#xff0c;找到数…

2022蓝桥杯大赛软件类国赛Java大学B组 左移右移 空间换时间+双指针

import java.util.Scanner;public class Main {static Scanner scnew Scanner(System.in);public static void main(String[] args) {int nsc.nextInt();//数组长度int tsc.nextInt();//操作次数int arr[]new int[n];char arr1[] new char[t];int arr2[] new int[t];int vis…

输入一串字符,输入想要字符串前*的个数n,判断字符串前*的个数是大于n还是小于n,如果大于n则删除多余的*其它保持不变,如果小于n,则字符串也保持不变

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> void fun(char* a, int n) {int i 0, j 0, m 0,b0,c0;char* p;p a;//第一步&#xff0c;判断字母前面有多少个*while (p[i] *){j;}printf("字母前*的个数%d\n",j);//求总的字符串长度while (a[m] !…

基于.net开发的博客系统

基于.net开发可以批量上传md文件生成文章的博客系统 .NET 个人博客 基于.net开发的博客系统 个人博客系统&#xff0c;采用.net core微服务技术搭建&#xff0c;采用传统的MVC模式&#xff0c;使用EF core来对mysql数据库(sqlite数据库)进行CRUD操作项目 为什么要自己开发博客…

csdn的insCode怎么用IDE和linux终端

1.进入insCode&#xff0c;选择工作台 找到我的项目&#xff0c;没有项目的话可以新建一个。 选择在IDE中编辑&#xff0c;界面如下&#xff1a; 右边有个终端&#xff0c;点击即可出现linux的xterm终端。

依赖的各种java库(工具类) :fastjson,lombok,jedis,druid,mybatis等

lombok 功能&#xff1a; Lombok 是一个实用的Java类库&#xff0c;可以通过简单的注解来简化和消除一些必须有但显得很臃肿的Java代码。 导入包&#xff1a;使用Lombok首先要将其作为依赖添加到项目中&#xff0c;在pom.xml文件中手动添加 <dependency><groupId&g…

别对我动心短视频:成都鼎茂宏升文化传媒公司

别对我动心短视频&#xff1a;时代的爱情哲学与心理探索 在短视频的海洋里&#xff0c;"别对我动心"这样的标题&#xff0c;如同一颗石子投入平静的湖面&#xff0c;激起了层层涟漪。它不仅仅是对一段情感的拒绝&#xff0c;更是一种现代人情感态度的表达&#xff0…

AIGC-常见图像质量评估MSE、PSNR、SSIM、LPIPS、FID、CSFD,余弦相似度----理论+代码

持续更新和补充中…多多交流&#xff01; 参考: 图像评价指标PNSR和SSIM 函数 structural_similarity 图片相似度计算方法总结 MSE和PSNR MSE: M S E 1 m n ∑ i 0 m − 1 ∑ j 0 n − 1 [ I ( i , j ) − K ( i , j ) ] 2 MSE\frac{1}{mn}\sum_{i0}^{m-1}\sum_{j0}^{n-1}[…

TECHNIUM INTERNATIONAL: 利用 AI 和 TECHNIUM 矩阵协议引领区块链创新

在充满活力的加密货币和区块链技术领域&#xff0c;Technium International 以领军者的姿态迅速崛起&#xff0c;跻身科技巨头的顶尖行列。Technium International 成立于 2018 年&#xff0c;总部设于塞席尔&#xff0c;透过人工智慧&#xff08;AI&#xff09;和区块链技术的…

代码随想录算法训练营第三十七天|435. 无重叠区间、763.划分字母区间、56. 合并区间、738.单调递增的数字、968.监控二叉树

435. 无重叠区间 文档讲解&#xff1a;代码随想录 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 本道题与上个题目相似&#xff0c;都是求重叠区间 统计重叠区间的个数&#xff0c;减去重叠区间的个数就是无重叠区间了 主要就是为了让区间尽可能的重叠。&a…

Python_文件操作_学习

目录 一、关于文件的打开和关闭 1. 文件的打开 2.文件的关闭 二、文件的读取 1. 文件的读_r 2. 使用readline 3.使用readlines 三、文件的写入 1. 文本的新建写入 2.文本的追加写入 四、文件的删除和重命名 1.文件的重命名 2.文件的删除 五、文件的定位读写 1.t…

【pyspark速成专家】5_Spark之RDD编程3

目录 ​编辑 六&#xff0c;共享变量 七&#xff0c;分区操作 六&#xff0c;共享变量 当spark集群在许多节点上运行一个函数时&#xff0c;默认情况下会把这个函数涉及到的对象在每个节点生成一个副本。 但是&#xff0c;有时候需要在不同节点或者节点和Driver之间共享变…

电商公司需不需要建数字档案室呢

建立数字档案室对于电商公司来说是非常有必要的。以下是一些原因&#xff1a; 1. 空间节约&#xff1a;数字档案室可以将纸质文件转化为电子文件&#xff0c;节省了大量存储空间。这对于电商公司来说尤为重要&#xff0c;因为他们通常会有大量的订单、客户信息和供应商合同等文…

OpenHarmony系统使用gdb调试init

前言 OpenAtom OpenHarmony&#xff08;简称“OpenHarmony”&#xff09;适配新的开发板时&#xff0c;启动流程init大概率会出现问题&#xff0c;其为内核直接拉起的第一个用户态进程&#xff0c;问题定位手段只能依赖代码走读和增加调试打印&#xff0c;初始化过程中系统崩溃…

封装了一个iOS中间放大的collectionView layout

效果图如下所示 原理&#xff1a;就是首先确定一个放大和缩小系数和原大小对应的基准位置&#xff0c;然后根据距离每个布局属性到视图中心的距离和基准点到中心的距离的差距/基准点到中心的距离&#xff0c; 计算出每个布局属性的缩放系数 下面是代码 // // LBHorizontalCe…

数据库--数据库基础(一)

目录 第一章 绪论 一.数据库的基本概念 1. 数据库的4个基本概念 2、数据库系统的特点 二.数据库和文件 三.数据模型 1.概念模型 2.逻辑模型(物理模型) 2.1关系模型 四.数据库系统的三级模式结构&#xff1a; 五数据库的二级映像功能与数据独立性 第二章 关系数据库…