【人工智能实验】遗传算法求解旅行商问题 golang

news2025/4/17 16:18:45

人工智能经典问题旅行商求解

使用遗传算法求解,算法流程如下:

  1. 读取所有的城市坐标,记作集合initCitys
  2. 生成popSize个种群,记为pops
  3. 取出种群中适应度最高的,记作rank0
  4. 使用轮盘算法,从rank0中选出eliteSize个个体,生成下一代,记作pops2
  5. 对pops2进行交叉
  6. 对pops2进行概率为rate的随机变异
  7. 如果繁衍次数大于generation,则跳出,反之回到3

进阶

因为有随机事件,所以即使是同样的参数(initCitys, popSize, eliteSize, rate, generation)也可能得到不同的结果。

因此,为了提高运行效率,我采用了并发的方式,对同一参数进行多次并发,并得到最终的结果。

使用go语言实现如下

  • main.go

    package main
    
    import (
    	"fmt"
    	"github.com/gookit/color"
    	"log"
    	"os"
    	"os/signal"
    	"runtime"
    	"sync"
    	"syscall"
    )
    
    var (
    	wg            sync.WaitGroup
    	numGoroutines = 25
    )
    var (
    	msgChan  = make(chan string)
    	quitChan = make(chan os.Signal, 1)
    	doneChan = make(chan bool)
    )
    
    func main() {
    	runtime.LockOSThread()
    	color.BgCyan.Println("Y02114562 ")
    	cityList, err := readCityData("city.txt")
    	if err != nil {
    		log.Fatalln("Read file error ", err)
    	}
    	wg.Add(numGoroutines)
    	for i := 0; i < numGoroutines; i++ {
    		go goSolve(i, CopyCity(cityList), 100, 20, 0.01, 3000)
    	}
    
    	signal.Notify(quitChan, syscall.SIGINT, syscall.SIGTERM)
    	go func() {
    		wg.Wait()
    		doneChan <- true
    	}()
    
    sel:
    	select {
    	case <-quitChan:
    		color.Red.Println("接收到终止信号")
    		break
    	case msg := <-msgChan:
    		color.BgLightYellow.Println(msg)
    		goto sel
    	case <-doneChan:
    		color.Green.Println("所有协程执行完毕")
    		break
    	}
    	<-quitChan
    	runtime.UnlockOSThread()
    }
    
    func goSolve(goID int, cityList []City, popSize, eliteSize int, rate float64, generation int) {
    	defer wg.Done()
    	msgChan <- fmt.Sprintf("协程%d开启求解", goID)
    	float64s := solve(goID, cityList, popSize, eliteSize, rate, generation)
    	runtime.Gosched()
    	min := findMin(float64s)
    	msgChan <- fmt.Sprintf("协程%d的最短路径为%f", goID, min)
    }
    
    func solve(goID int, cityList []City, popSize, eliteSize int, rate float64, generation int) []float64 {
    	population := initPop(cityList, popSize)
    	process := make([]float64, 0)
    
    	for i := 0; i < generation; i++ {
    		population = updatePop(population, eliteSize, rate)
    		runtime.Gosched()
    		process = append(process, 1.0/rank(population)[0].Fitness)
    	}
    	idxRankPop := rank(population)[0].Index
    	cities := population[idxRankPop]
    	var index = 0
    	writeToFile(fmt.Sprintf("out/%d.txt", goID), func() (bool, float64, float64) {
    		if index >= len(cities) {
    			return false, 0, 0
    		}
    		x, y := cities[index].x, cities[index].y
    		index++
    		runtime.Gosched()
    		return true, x, y
    	})
    	return process
    }
    
  • city.go

    package main
    
    type City struct {
    	x float64
    	y float64
    }
    
    func NewCity(x, y float64) City {
    	return City{x: x, y: y}
    }
    
    func CopyCity(input []City) []City {
    	copied := make([]City, len(input))
    	copy(copied, input)
    	return copied
    }
    
  • tool.go

    package main
    
    import (
    	"bufio"
    	"fmt"
    	"log"
    	"math"
    	"math/rand"
    	"os"
    	"sort"
    	"strconv"
    	"strings"
    )
    
    // distance 城市的距离
    func distance(ca, cb City) float64 {
    	dx := math.Abs(ca.x - cb.x)
    	dy := math.Abs(ca.y - cb.y)
    	return math.Sqrt(math.Pow(dx, 2) + math.Pow(dy, 2))
    }
    
    // distancePop 个体的值
    func distancePop(pop []City) float64 {
    	sum := 0.0
    	for i := 0; i < len(pop); i++ {
    		sum += distance(pop[i], pop[(i+1)%len(pop)])
    	}
    	return sum
    }
    
    // fitness 个体的适应度
    func fitness(pop []City) float64 {
    
    	return 1.0 / distancePop(pop)
    }
    
    // rank 种群的排序
    func rank(population [][]City) []struct {
    	Index   int
    	Fitness float64
    } {
    	rankPopSlice := make([]struct {
    		Index   int
    		Fitness float64
    	}, len(population))
    
    	for i, pop := range population {
    		fit := fitness(pop)
    		rankPopSlice[i] = struct {
    			Index   int
    			Fitness float64
    		}{i, fit}
    	}
    
    	sort.Slice(rankPopSlice, func(i, j int) bool {
    		return rankPopSlice[i].Fitness > rankPopSlice[j].Fitness
    	})
    
    	return rankPopSlice
    }
    
    // initPop 初始化种群
    func initPop(cityList []City, popSize int) [][]City {
    	pop := make([][]City, popSize)
    
    	for i := 0; i < popSize; i++ {
    		newCityList := make([]City, len(cityList))
    		copy(newCityList, cityList)
    		rand.Shuffle(len(newCityList), func(j, k int) {
    			newCityList[j], newCityList[k] = newCityList[k], newCityList[j]
    		})
    		pop[i] = newCityList
    	}
    
    	return pop
    }
    
    // updatePop 更新种群
    func updatePop(oldPop [][]City, eliteSize int, rate float64) [][]City {
    	oldRank := rank(oldPop)
    	selectedPop := selectPopulation(oldPop, oldRank, eliteSize)
    	breadPop := cross(selectedPop, eliteSize)
    	return mutate(breadPop, rate)
    }
    
    // mutate 变异
    func mutate(pop [][]City, mutationRate float64) [][]City {
    	mutationPop := make([][]City, 0)
    
    	for i := 0; i < len(pop); i++ {
    		for j := 0; j < len(pop[i]); j++ {
    			rate := rand.Float64()
    			if rate < mutationRate {
    				a := rand.Intn(len(pop[i]))
    				pop[i][a], pop[i][j] = pop[i][j], pop[i][a]
    			}
    		}
    		mutationPop = append(mutationPop, pop[i])
    	}
    
    	return mutationPop
    }
    
    // cross 交叉操作
    func cross(pop [][]City, eliteSize int) [][]City {
    	breedPop := make([][]City, 0)
    	for i := 0; i < eliteSize; i++ {
    		breedPop = append(breedPop, pop[i])
    	}
    
    	i := 0
    	for i < (len(pop) - eliteSize) {
    		// 随机选择两个个体 a 和 b 进行交叉
    		a := rand.Intn(len(pop))
    		b := rand.Intn(len(pop))
    		if a != b {
    			fa := pop[a]
    			fb := pop[b]
    			// 随机选择交叉点,确定交叉片段的起始和结束位置
    			geneA := rand.Intn(len(pop[a]))
    			geneB := rand.Intn(len(pop[b]))
    			startGene := min(geneA, geneB)
    			endGene := max(geneA, geneB)
    			child1 := make([]City, 0)
    			// 将交叉片段从个体 a 复制到子代个体 child1 中
    			for j := startGene; j < endGene; j++ {
    				child1 = append(child1, fa[j])
    			}
    			child2 := make([]City, 0)
    			// 将个体 b 中不在 child1 中的城市复制到子代个体 child2 中
    			for _, j := range fb {
    				if !contains(child1, j) {
    					child2 = append(child2, j)
    				}
    			}
    			// 将 child1 和 child2 合并形成一个新的子代个体,加入到子代种群中
    			breedPop = append(breedPop, append(child1, child2...))
    			i = i + 1
    		}
    	}
    
    	return breedPop
    }
    
    func min(a, b int) int {
    	if a < b {
    		return a
    	}
    	return b
    }
    
    func max(a, b int) int {
    	if a > b {
    		return a
    	}
    	return b
    }
    
    func contains(slice []City, elem City) bool {
    	for _, e := range slice {
    		if e == elem {
    			return true
    		}
    	}
    	return false
    }
    
    // selectPopulation 精英选择策略(轮盘)
    func selectPopulation(pop [][]City, popRank []struct {
    	Index   int
    	Fitness float64
    }, eliteSize int) [][]City {
    	selectPop := make([][]City, 0)
    
    	for i := 0; i < eliteSize; i++ {
    		selectPop = append(selectPop, pop[popRank[i].Index])
    	}
    
    	cumSum := 0.0
    	cumSumList := make([]float64, 0) // 求累计概率
    	tempPop := make([]struct {
    		Index   int
    		Fitness float64
    	}, len(popRank))
    	copy(tempPop, popRank)
    
    	for i := 0; i < len(tempPop); i++ {
    		cumSum += tempPop[i].Fitness
    		cumSumList = append(cumSumList, cumSum)
    	}
    
    	for i := 0; i < len(tempPop); i++ {
    		cumSumList[i] /= cumSum
    	}
    
    	// 剩余的按顺序返回
    	for i := len(tempPop) - eliteSize; i > 0; i-- {
    		rate := rand.Float64()
    		for j := 0; j < len(tempPop); j++ {
    			if cumSumList[j] > rate {
    				selectPop = append(selectPop, pop[tempPop[i].Index])
    				break
    			}
    		}
    	}
    
    	return selectPop
    }
    
    func readCityData(filepath string) ([]City, error) {
    	cityList := make([]City, 0)
    
    	file, err := os.Open(filepath)
    	if err != nil {
    		return nil, err
    	}
    	defer func(file *os.File) {
    		_ = file.Close()
    	}(file)
    
    	scanner := bufio.NewScanner(file)
    	for scanner.Scan() {
    		line := scanner.Text()
    		line = strings.TrimSpace(line)
    		cityInfo := strings.Split(line, "\t")
    		if len(cityInfo) != 3 {
    			return nil, fmt.Errorf("invalid city data: %s", line)
    		}
    
    		x, err := strconv.ParseFloat(strings.TrimSpace(cityInfo[1]), 64)
    		if err != nil {
    			return nil, fmt.Errorf("invalid city X coordinate: %s", cityInfo[1])
    		}
    
    		y, err := strconv.ParseFloat(strings.TrimSpace(cityInfo[2]), 64)
    		if err != nil {
    			return nil, fmt.Errorf("invalid city Y coordinate: %s", cityInfo[2])
    		}
    
    		cityList = append(cityList, NewCity(x, y))
    	}
    
    	if err := scanner.Err(); err != nil {
    		return nil, err
    	}
    
    	return cityList, nil
    }
    
    func findMin(slice []float64) float64 {
    	min := math.Inf(1)
    	for _, value := range slice {
    		if value < min {
    			min = value
    		}
    	}
    	return min
    }
    
    func writeToFile(name string, getData func() (bool, float64, float64)) {
    	file, err := os.Create(name)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer func(file *os.File) {
    		_ = file.Close()
    	}(file)
    	for true {
    		if has, x, y := getData(); has {
    			line := fmt.Sprintf("%f|%f\n", x, y)
    			_, err := file.WriteString(line)
    			if err != nil {
    				log.Fatal(err)
    			}
    		} else {
    			return
    		}
    	}
    }
    
  • city.txt

    A 	18	54
    B 	87	76
    C 	74	78
    D 	71	71
    E 	25	38
    F 	55	35
    G 	4	50
    H 	13	40
    I 	18	40
    J 	24	42
    K 	71	44
    L 	64	60
    M 	68	58
    N 	83	69
    O 	58	69
    P 	54	62
    Q 	51	67
    R 	37	84
    S 	41	94
    T 	2	99
    U 	7	64
    V 	22	60
    W 	25	62
    X 	62	32
    Y 	87	7
    Z 	91	38
    A2 	83	45
    B2 	41	26
    C2 	45	21
    D2 	44	35
    
  • city2.txt

    北京	 116.46	39.92
    天津 	117.2	39.13
    上海 	121.48	31.22
    重庆 	106.54	29.59
    拉萨	 91.11	29.97
    乌鲁木齐 	87.68	43.77
    银川 	106.27	38.47
    呼和浩特 	111.65	40.82
    南宁	 108.33	22.84
    哈尔滨	126.63	45.75
    长春 	125.35	43.88
    沈阳 	123.38	41.8
    石家庄	114.48	38.03
    太原	 112.53	37.87
    西宁 	101.74	36.56
    济南	 117	36.65
    郑州	 113.6	34.76
    南京 	118.78	32.04
    合肥 	117.27	31.86
    杭州 	120.19	30.26
    福州 	119.3	26.08
    南昌	 115.89	28.68
    长沙	 113	28.21
    武汉	 114.31	30.52
    广州	 113.23	23.16
    台北	 121.5	25.05
    海口	 110.35	20.02
    兰州 	103.73	36.03
    西安 	108.95	34.27
    成都 	104.06	30.67
    贵阳 	106.71	26.57
    昆明	 102.73	25.04
    香港	 114.1	22.2
    澳门 	113.33	22.13
    

因为go语言的gui绘制能力过于羸弱,我采用了python进行绘制,代码如下

import matplotlib.pyplot as plt

if __name__ == '__main__':
    x_labels = []
    y_labels = []

    with open(r'D:\Desktop\GolandProjects\AI2\out\24.txt', 'r') as file:
        for line in file:
            parts = line.strip().split('|')
            x_labels.append(float(parts[0]))
            y_labels.append(float(parts[1]))
    x_labels.append(x_labels[0])
    y_labels.append(y_labels[0])

    plt.figure()

    plt.scatter(x_labels, y_labels, c='r', marker='*', s=200, alpha=0.5)
    plt.plot(x_labels, y_labels, "b", ms=20)

    plt.savefig('_route.jpg')
    plt.show()

运行结果

go语言结果
python语言结果

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

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

相关文章

Redis分布式锁(中)

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 我们在不久前介绍了Spr…

【场景】高并发解决方案

文章目录 1. 硬件2. 缓存2.1 HTTP缓存2.1.1 浏览器缓存2.1.2 Nginx缓存2.1.3 CDN缓存 2.2 应用缓存 3 集群4. 拆分4.1 应用拆分&#xff08;分布式、微服务&#xff09;4.2 数据库拆分 5. 静态化6. 动静分离7. 消息队列8. 池化8.1 对象池8.2 数据库连接池8.3 线程池 9. 数据库优…

酷开系统 酷开科技,将家庭娱乐推向新高潮

在当今数字化时代&#xff0c;家庭娱乐已经成为人们日常生活中不可或缺的一部分。如果你厌倦了传统的家庭娱乐方式&#xff0c;想要一种全新的、充满惊喜的娱乐体验&#xff0c;那么&#xff0c;不妨进入到酷开科技的世界&#xff0c;作为智能电视行业领军企业&#xff0c;酷开…

Git常用操作-MD

文章目录 1. 本地创建分支&#xff0c;编写代码&#xff0c;提交本地分支到远程仓库2. 提交本地代码到本地仓库3. 提交本地代码到本地dev分支4. 提交本地dev分支到远程仓库5. 本地dev分支拉取远程master分支&#xff0c;并将master分支内容合并到本地dev6. 同义命令7. 撤销上次…

Codeforces Round 908 (Div 2——AB)

A. Secret Sport 题目 AB二人玩游戏&#xff0c;每一局&#xff08;plays&#xff09;游戏会有一个获胜者&#xff0c;首先获胜X局&#xff08;play&#xff09;的玩家得一分&#xff08;赢得一轮sets&#xff09;。率先获得Y分的玩家获得最终胜利。 给你整场游戏的每局&…

Spring Boot使用EhCache完成一个缓存集群

在上一篇在SpringBoot中使用EhCache缓存&#xff0c;我们完成了在Spring Boot中完成了对EhCaChe的使用&#xff0c;这篇&#xff0c;我们将对EhCache的进一步了解&#xff0c;也就是搭建一个EhCache的缓存集群。 集群 在搭建一个EhCache的时候&#xff0c;我们需要先了解&…

areadetector ADURL模块应用在面探测控制的初步应用

本章中讨论了使用ADURL控制面探测器Lambda的过程&#xff1a; ADURL的使用请见&#xff1a; EPICS -- areaDetector URL驱动程序-CSDN博客 需要启动一个ADURL的IOC程序&#xff0c;并且设置相关的插件中参数的值&#xff1a; # st.cm < envPaths < st_base.cmddbpf 1…

WorkPlus即时通讯app支持多种信创环境组合运行

在信息技术领域&#xff0c;国产信创技术的快速发展为企业带来了更多的选择和机会。在此背景下&#xff0c;WorkPlus作为一款全方位的移动数字化平台&#xff0c;全面支持国产信创操作系统、芯片和数据库&#xff0c;并且全面兼容鸿蒙操作系统。这一优势使得WorkPlus成为了企业…

企业数据备份应该怎么操作?应该知道的四种备份方法

​企业数据备份对于保护最重要的资产至关重要。在面对不断增加的安全威胁时&#xff0c;很多企业都感到无从下手&#xff0c;不知如何保护关键业务数据。通过采用正确的数据备份方法&#xff0c;可以成为确保企业数据安全的最有效手段。因此&#xff0c;不论您是个人还是在职人…

第二证券:今日投资前瞻:小米汽车引关注 全球风光有望持续高速发展

昨日&#xff0c;两市股指盘中轰动上扬&#xff0c;深成指、创业板指一度涨超1%。到收盘&#xff0c;沪指涨0.55%报3072.83点&#xff0c;深成指涨0.72%报10077.96点&#xff0c;创业板指涨0.53%报2015.36点&#xff0c;北证50指数涨2.64%&#xff1b;两市算计成交9900亿元&…

亚马逊云科技云存储服务指南

文章作者&#xff1a;Libai 高效的云存储服务对于现代软件开发中的数据管理至关重要。亚马逊云科技云存储服务提供了强大的工具&#xff0c;可以简化工作流程并增强数据管理能力。 亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、开发案例、技术专栏…

Ubuntu 22.04 LTS ffmpeg mp4 gif 添加图片水印

ffmpeg编译安装6.0.1&#xff0c;参考 Ubuntu 20.04 LTS ffmpeg gif mp4 互转 许编译安装ffmpeg &#xff1b;解决gif转mp4转换后无法播放问题-CSDN博客 准备一个logo MP4添加水印 ffmpeg -i 2.mp4 -vf "movielogo.png[watermark];[in][watermark]overlayx10:y10[out]&…

torch_cluster、torch_scatter、torch_sparse三个包的安装

涉及到下面几个包安装的时候经常会出现问题&#xff0c;这里我使用先下载然后再安装的办法&#xff1a; pip install torch_cluster pip install torch_scatter pip install torch_sparse 1、选择你对应的torch版本&#xff1a;https://data.pyg.org/whl/ 2、点进去然后&…

PowerConsume功耗计算器

嵌入式低功耗产品开发&#xff0c;功耗计算器资源-CSDN文库 PowerConsume使用说明 安装说明 需要安装在无空格等特殊字符的路径&#xff0c;不推荐安装在C盘。 功能说明 已知条件 电池容量 各状态的电流和运行时间 自动计算出设备运行时间 启动界面如下 添加状态 在空白处…

【Python基础篇】运算符

博主&#xff1a;&#x1f44d;不许代码码上红 欢迎&#xff1a;&#x1f40b;点赞、收藏、关注、评论。 格言&#xff1a; 大鹏一日同风起&#xff0c;扶摇直上九万里。 文章目录 一 Python中的运算符二 算术运算符1 Python所有算术运算符的说明2 Python算术运算符的所有操作…

12.Oracle的索引

Oracle11g的索引 一、什么是索引二、索引的分类三、索引的语法四、分析索引四、索引的作用及使用场景 一、什么是索引 在Oracle数据库中&#xff0c;索引是一种特殊的数据结构&#xff0c;用于提高查询性能和加速数据检索。索引存储了表中某列的值和对应的行指针&#xff0c;这…

开源微信小程序源码/校园综合服务平台小程序源码+数据库/包括校园跑腿 快递代取 打印服务等功能

源码简介&#xff1a; 校园综合服务小程序源码&#xff0c;它是基于微信小程序开发&#xff0c;包括快递代取 打印服务 校园跑腿 代替服务 上门维修和其他帮助等功能。它是开源微信小程序源码。 校园综合服务小程序开源源码是一款功能强大的小程序&#xff0c;可用于搭建校园…

【uniapp/uview1.x】u-upload 在 v-for 中的使用时, before-upload 如何传参

引入&#xff1a; 是这样一种情况&#xff0c;在接口获取数据之后&#xff0c;是一个数组列表&#xff0c;循环展示后&#xff0c;需要在每条数据中都要有图片上传&#xff0c;互不干扰。 分析&#xff1a; uview 官网中有说明&#xff0c;before-upload 是不加括号的&#xff…

@Version乐观锁配置mybatis-plus使用(version)

1&#xff1a;首先在实体类的属性注解上使用Version import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.Versio…

番外 2 : LoadRunner 的安装以及配置

LoadRunner 的安装以及配置教程 一 . 配置 IE 浏览器二 . 安装 LoadRunner 工具三 . 修改默认浏览器的配置四 . 设置 LoadRunner 能够获取本地资源 Hello , 大家好 , 又给大家带来新的专栏喽 ~ 这个专栏是专门为零基础小白从 0 到 1 了解软件测试基础理论设计的 , 虽然还不足以…