遗传算法(GA)求解TSP问题(golang实现)

1. 遗传算法概述

遗传算法(Genetic Algorithm, GA)是一种经典的基于群体的优化算法,群体优化算法的描述如图所示。遗传算法用于寻找复杂优化问题的解决方案,受自然选择和遗传学原理的启发,遗传算法的求解策略类似于“物竞天择,适者生存”。

遗传算法用染色体表示候选解的总体。染色体通常使用二进制编码为或数值字符串表示,一条染色体代表当前问题的一种可能解决方案。遗传算法使用适应度函数来评价解的质量,目标是找到适应度值最高的染色体,代表问题的最优解。遗传算法的基本步骤如下:
步骤1:初始化,随机生成染色体的起始种群。
步骤2:评价,利用适应度函数评价种群中每个染色体的适应度。
步骤3:选择(轮盘赌),选择适应度高的染色体被选择为父代染色体,并用于产生新的后代染色体。
步骤4:交叉,交叉是一种能够使得父代染色体产生新的后代的遗传算子,交叉结合了父代染色体的遗传信息,产生新的染色体,代表新的解决方案。
步骤5:变异,通过改变染色体遗传信息的一种遗传算子,变异引入新的遗传信息以增加种群的多样性。
步骤6:更新,计算后代染色体的适应度函数,并替换掉适应度低的染色体,更新种群。
步骤7:重复步骤3—步骤6,直到达到算法的终止条件,如种群中的染色体适应度不再变化,或达到预设的迭代次数。

2.具体步骤

2.1 数据源

NAME: china34
TYPE: TSP
COMMENT: 34 locations in China
DIMENSION: 34
EDGE_WEIGHT_TYPE: WGS
NODE_COORD_SECTION
1 101.74 36.56
2 103.73 36.03
3 106.27 38.47
4 108.95 34.27
5 113.65 34.76
6 117.00 36.65
7 114.48 38.03
8 112.53 37.87
9 111.65 40.82
10 116.41 39.90
11 117.20 39.08
12 123.38 41.80
13 125.35 43.88
14 126.63 45.75
15 121.47 31.23
16 120.19 30.26
17 118.78 32.04
18 117.27 31.86
19 114.31 30.52
20 113.00 28.21
21 115.89 28.68
22 119.30 26.08
23 121.30 25.03
24 114.17 22.32
25 113.55 22.20
26 113.23 23.16
27 110.35 20.02
28 108.33 22.84
29 106.71 26.57
30 106.55 29.56
31 104.06 30.67
32 102.73 25.04
33 91.11 29.97
34 87.68 43.77
EOF

这是一组中国省会的经纬度坐标,注意在计算距离矩阵时使用Haversine 公式,不过这不重要。

const earthRadiusKm = 6378.14

// degreeToRadian 将角度转换为弧度
func degreeToRadian(deg float64) float64 {
	return deg * (math.Pi / 180.0)
}

func WGSDistance(lat1, lon1, lat2, lon2 float64) float64 {
	// 将经纬度转换为弧度
	lat1Rad := degreeToRadian(lat1)
	lon1Rad := degreeToRadian(lon1)
	lat2Rad := degreeToRadian(lat2)
	lon2Rad := degreeToRadian(lon2)

	// // 计算差值
	dLat := lat2Rad - lat1Rad
	dLon := lon2Rad - lon1Rad

	// 使用 Haversine 公式计算距离
	a := math.Sin(dLat/2)*math.Sin(dLat/2) +
		math.Cos(lat1Rad)*math.Cos(lat2Rad)*
			math.Sin(dLon/2)*math.Sin(dLon/2)
	c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))

	// 距离为地球半径乘以角度
	distance := earthRadiusKm * c

	return distance
}

2.2 初始化种群

通过随机生成的方式,生成一个种群,用于繁殖。通常TSP问题中个体的编码方式

// 随机排列
func InitPath(nodeNum int) []int {
	path := make([]int, 0)
	for i := 0; i < nodeNum; i++ {
		path = append(path, i)
	}
	Shuffle(path)
	return path
}

func Shuffle(path []int) {
	for i := 0; i < len(path); i++ {
		j := RandInt(0, len(path)-1)
		path[i], path[j] = path[j], path[i]
	}
}

2.3 繁殖

繁殖包括选择和交叉的过程,生成新的个体。
● 选择通过轮盘赌选择,即值越大越容易被选择到

func (ga *geneticAlgorithm) RouletteSelect() int {
	roulette := make([]float64, len(ga.Population))
	for i, ch := range ga.Population {
		roulette[i] = 1 / ga.CalcObjective(ch.Path)
	}
	sum := 0.0
	for _, v := range roulette {
		sum += v
	}
	position := common.RandDecimal() * sum
	vernier := 0.0
	for i, v := range roulette {
		vernier += v
		if position <= vernier {
			return i
		}
	}
	return 0
}

● 选择出两个父代个体,交叉生成子代

● 精英保留,对于适应度高的那些个体,直接进入下一代

// 交叉/繁殖
func (ga *geneticAlgorithm) SelectAndCrossover() {
	nextGeneration := make([]Chromosome, 0)
	for i := 0; i < len(ga.Population) - len(ga.Elite); i++ {
		// 轮盘赌选择父母
		fai, moi := 0, 0
		for fai == moi {
			fai, moi = ga.RouletteSelect(), ga.RouletteSelect()
		}
		fa, mo := ga.Population[fai], ga.Population[moi]
		if RandDecimal() > float64(ga.Params["cross_probability"]) / 100 {
			nextGeneration = append(nextGeneration, fa)
			continue
		}
		position1 := RandInt(0, len(ga.DistMatrix))
		position2 := position1
		for position2 == position1 {
			position2 = RandInt(0, len(ga.DistMatrix))
		}
		if position1 > position2 {
			position1, position2 = position2, position1
		}

		cut := fa.Path[position1:position2]

		child := Chromosome{Path: make([]int, len(mo.Path))}
		for i, pivot := 0, 0; pivot < position1; i ++ {
			if NotIn(cut, mo.Path[i]) {
				child.Path[pivot] = mo.Path[i]
				pivot ++
			}
		}

		for i, pivot := 0, position1; pivot < position2; i ++ {
			child.Path[pivot] = cut[i]
			pivot ++
		}
		for i, pivot := len(mo.Path)-1, len(mo.Path)-1; pivot >= position2; i -- {
			if NotIn(cut, mo.Path[i]) {
				child.Path[pivot] = mo.Path[i]
				pivot --
			}
		}
		nextGeneration = append(nextGeneration, child)
	}
	nextGeneration = append(nextGeneration, ga.Elite...)
	ga.Population = nextGeneration
}

2.4 变异

变异的方法有很多种,实际上就是对个体进行一个微小的改变,此处的做法是随机交换两个节点的位置

// 变异
func (ga *geneticAlgorithm) Mutate() {
	mp := float64(ga.Params["mutate_probability"]) / 100
	for i := 0; i < len(ga.Population); i++ {
		if RandDecimal() < mp {
			ga.Operator.TwoSwap(ga.Population[i].Path)
		}
	}
}

type CalcObjFunc func(path []int) float64

type Operator struct {
	DistMatrix [][]float64
	CalcObjFunc
}

func (opt Operator) TwoSwap(path []int) {
	a := RandInt(0, len(path)-1)
	for {
		b := RandInt(0, len(path)-1)
		if a != b {
			path[a], path[b] = path[b], path[a]
			return
		}
	}
}

3. 完整代码

package main

import (
	"bufio"
	"flag"
	"fmt"
	"math"
	"math/rand"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"
)

func main() {
	var filepath string
	var coor string
	flag.StringVar(&filepath, "filepath", "./china34.tsp", "指定tsp文件路径")
	flag.StringVar(&coor, "coor", "WGS", "指定坐标系")
	flag.Parse()
	// 初始化节点信息和算子
	nodes := ReadTsp(filepath)
	matrix := GenerateDistMatrix(nodes, coor)
	fmt.Println(len(matrix))
	gaParams := map[string]int{
		"population_size":    100,
		"eliteNum":           10,
		"mutate_probability": 5,
		"cross_probability" : 70,
		"iteration":          1000,
	}
	ga := NewGA(matrix, gaParams)
	ga.Run()
}

type Matrix [][]float64

type GeneticAlg interface {
	InitSolution()       // 初始化种群
	SelectAndCrossover() // 选择父代并交叉生成子代
	Mutate()             // 变异
	Run()
	CalcObjective(path []int) float64 // 计算目标函数(适应度)
}

// 定义一个染色体,在TSP/VRP中指一条路径
type Chromosome struct {
	Path   []int
	Routes [][]int
}

// 用来描述一次算法的运行
type geneticAlgorithm struct {
	DistMatrix [][]float64
	Population []Chromosome
	Elite      []Chromosome
	Params     map[string]int // 定义算法的一些参数
	Status     int            // 0: init 1: running 2: error
	Process    int
	Error      error
	Operator   Operator
}

var _ GeneticAlg = (*geneticAlgorithm)(nil)

func NewGA(matrix Matrix, params map[string]int) GeneticAlg {
	return &geneticAlgorithm{
		DistMatrix: matrix,
		Params:     params,
	}
}

func (ga *geneticAlgorithm) Run() {
	ga.Operator = Operator{
		DistMatrix:  ga.DistMatrix,
		CalcObjFunc: ga.CalcObjective,
	}
	ga.InitSolution() // init population
	iteration := ga.Params["iteration"]
	for i := 1; i <= iteration; i++ {
		ga.KeepElite()
		ga.SelectAndCrossover() // select and crossover
		ga.KeepElite()
		ga.Mutate()           // mutate Chromosome
	}
	ga.KeepElite()
	fmt.Println("best solution:", ga.CalcObjective(ga.Elite[0].Path))
	
	fmt.Println(ga.Elite[0].Path)
}

func (ga *geneticAlgorithm) InitSolution() {
	popSize := ga.Params["population_size"]
	population := make([]Chromosome, 0)
	for i := 0; i < popSize; i++ {
		// 此处随机生成路径,你也可以编写其他初始化种群的方法
		randomPath := InitPath(len(ga.DistMatrix))
		ch := Chromosome{
			Path: randomPath,
		}
		population = append(population, ch)
	}
	ga.Population = population
}

// 选择
func (ga *geneticAlgorithm) KeepElite() {
	sort.Slice(ga.Population, ga.Sort) // 将种群按适应度从小到大排序
	// 精英保留
	eliteNum := ga.Params["eliteNum"]
	thisGenerationElites := ga.Population[:eliteNum]
	if len(ga.Elite) == 0 {
		ga.Elite = make([]Chromosome, eliteNum)
		copy(ga.Elite, thisGenerationElites)
		return
	}
	for i, elite := range ga.Elite {
		if ga.CalcObjective(thisGenerationElites[0].Path) < ga.CalcObjective(elite.Path) {
			for j, k := i, 0; j < eliteNum; j++ {
				ga.Elite[j] = thisGenerationElites[k]
				k++
			}
		}
	}
}

// 交叉/繁殖
func (ga *geneticAlgorithm) SelectAndCrossover() {
	nextGeneration := make([]Chromosome, 0)
	for i := 0; i < len(ga.Population) - len(ga.Elite); i++ {
		// 轮盘赌选择父母
		fai, moi := 0, 0
		for fai == moi {
			fai, moi = ga.RouletteSelect(), ga.RouletteSelect()
		}
		fa, mo := ga.Population[fai], ga.Population[moi]
		if RandDecimal() > float64(ga.Params["cross_probability"]) / 100 {
			nextGeneration = append(nextGeneration, fa)
			continue
		}
		position1 := RandInt(0, len(ga.DistMatrix))
		position2 := position1
		for position2 == position1 {
			position2 = RandInt(0, len(ga.DistMatrix))
		}
		if position1 > position2 {
			position1, position2 = position2, position1
		}

		cut := fa.Path[position1:position2]

		child := Chromosome{Path: make([]int, len(mo.Path))}
		for i, pivot := 0, 0; pivot < position1; i ++ {
			if NotIn(cut, mo.Path[i]) {
				child.Path[pivot] = mo.Path[i]
				pivot ++
			}
		}

		for i, pivot := 0, position1; pivot < position2; i ++ {
			child.Path[pivot] = cut[i]
			pivot ++
		}
		for i, pivot := len(mo.Path)-1, len(mo.Path)-1; pivot >= position2; i -- {
			if NotIn(cut, mo.Path[i]) {
				child.Path[pivot] = mo.Path[i]
				pivot --
			}
		}
		nextGeneration = append(nextGeneration, child)
	}
	nextGeneration = append(nextGeneration, ga.Elite...)
	ga.Population = nextGeneration
}

// 变异
func (ga *geneticAlgorithm) Mutate() {
	mp := float64(ga.Params["mutate_probability"]) / 100
	for i := 0; i < len(ga.Population); i++ {
		if RandDecimal() < mp {
			ga.Operator.TwoSwap(ga.Population[i].Path)
		}
	}
}

func (ga *geneticAlgorithm) CalcObjective(path []int) float64 {
	return CalcDist(ga.DistMatrix, path)
}

func (ga *geneticAlgorithm) Sort(i, j int) bool {
	return ga.CalcObjective(ga.Population[i].Path) < ga.CalcObjective(ga.Population[j].Path)
}

func (ga *geneticAlgorithm) RouletteSelect() int {
	roulette := make([]float64, len(ga.Population))
	for i, ch := range ga.Population {
		roulette[i] = 1 / ga.CalcObjective(ch.Path)
	}
	sum := 0.0
	for _, v := range roulette {
		sum += v
	}
	position := RandDecimal() * sum
	vernier := 0.0
	for i, v := range roulette {
		vernier += v
		if position <= vernier {
			return i
		}
	}
	return 0
}

// 随机排列
func InitPath(nodeNum int) []int {
	path := make([]int, 0)
	for i := 0; i < nodeNum; i++ {
		path = append(path, i)
	}
	Shuffle(path)
	return path
}

func Shuffle(path []int) {
	for i := 0; i < len(path); i++ {
		j := RandInt(0, len(path)-1)
		path[i], path[j] = path[j], path[i]
	}
}

// 生成[start, end]范围内随机整数
func RandInt(start, end int) int {
	src := rand.NewSource(time.Now().UnixNano())
	rng := rand.New(src)
	return rng.Intn(end-start+1) + start
}

func RandDecimal() float64 {
	return rand.Float64()
}

func NotIn(set []int, i int) bool {
	for _, v := range set {
		if v == i {
			return false
		}
	}
	return true
}

type CalcObjFunc func(path []int) float64

type Operator struct {
	DistMatrix [][]float64
	CalcObjFunc
}

func (opt Operator) TwoSwap(path []int) {
	a := RandInt(0, len(path)-1)
	for {
		b := RandInt(0, len(path)-1)
		if a != b {
			path[a], path[b] = path[b], path[a]
			return
		}
	}
}

func CalcDist(distMatrix [][]float64, path []int) float64 {
	v := 0.0
	for i := 1; i < len(path); i++ {
		v += distMatrix[path[i-1]][path[i]]
	}
	return v + distMatrix[path[0]][path[len(path)-1]]
}

func GenerateDistMatrix(nodes []Node, edgeType string) [][]float64 {
	num := len(nodes)
	DistMatrix := make([][]float64, num)
	for i := range DistMatrix {
		DistMatrix[i] = make([]float64, num)
	}
	for i := 0; i < num; i++ {
		for j := i + 1; j < num; j++ {
			switch edgeType {
			case "EUC":
				DistMatrix[i][j] = EUCDistance(nodes[i].X, nodes[i].Y, nodes[j].X, nodes[j].Y)
				DistMatrix[j][i] = DistMatrix[i][j]
			case "WGS":
				DistMatrix[i][j] = WGSDistance(nodes[i].Y, nodes[i].X, nodes[j].Y, nodes[j].X)
				DistMatrix[j][i] = DistMatrix[i][j]
			}
		}
	}
	return DistMatrix
}

const earthRadiusKm = 6378.14

// degreeToRadian 将角度转换为弧度
func degreeToRadian(deg float64) float64 {
	return deg * (math.Pi / 180.0)
}

func WGSDistance(lat1, lon1, lat2, lon2 float64) float64 {
	// 将经纬度转换为弧度
	lat1Rad := degreeToRadian(lat1)
	lon1Rad := degreeToRadian(lon1)
	lat2Rad := degreeToRadian(lat2)
	lon2Rad := degreeToRadian(lon2)

	// // 计算差值
	dLat := lat2Rad - lat1Rad
	dLon := lon2Rad - lon1Rad

	// 使用 Haversine 公式计算距离
	a := math.Sin(dLat/2)*math.Sin(dLat/2) +
		math.Cos(lat1Rad)*math.Cos(lat2Rad)*
			math.Sin(dLon/2)*math.Sin(dLon/2)
	c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))

	// 距离为地球半径乘以角度
	distance := earthRadiusKm * c

	return distance
}

func EUCDistance(x1, y1, x2, y2 float64) float64 {
	return math.Sqrt(math.Pow(x1-x2, 2) + math.Pow(y1-y2, 2))
}

func ReadTsp(path string) []Node {
	nodes := make([]Node, 0)
	file, err := os.Open(path)
	if err != nil {
		panic("failed to read file")
	}
	defer file.Close()
	// 创建一个 Scanner 对象,用于逐行读取文件
	scanner := bufio.NewScanner(file)

	lines := make([]string, 0)
	// 逐行读取文件内容
	for scanner.Scan() {
		line := scanner.Text()
		lines = append(lines, line)
	}
	fmt.Println("reading file...")

	for i := 6; i < len(lines)-1; i++ {
		nodeInfo := strings.Split(lines[i], " ")
		id, _ := strconv.Atoi(nodeInfo[0])
		x, _ := strconv.ParseFloat(nodeInfo[1], 64)
		y, _ := strconv.ParseFloat(nodeInfo[2], 64)
		// id从0开始
		nodes = append(nodes, Node{Id: id - 1, X: x, Y: y})
	}
	return nodes
}

type Node struct {
	Id int
	X  float64
	Y  float64
}

● go run main.go 运行代码

最后

本人水平有效,文中可能存在一些错误

个人觉得标准的遗传算法求解TSP/VRP问题的效率一般,而且编码过程非常痛苦,尤其是交叉的部分,因为TSP要求基因点是不能重复的,即不能重复访问同一个点,因此交叉的过程需要考虑消除重复的节点。
可以参考之前的文章:自适应大邻域算法(ALNS)求解TSP问题,golang实现
效率还不错

参考

https://www.mathworks.com/help/gads/what-is-the-genetic-algorithm.html
https://zh.wikipedia.org/wiki/%E9%81%97%E4%BC%A0%E7%AE%97%E6%B3%95
https://www.geeksforgeeks.org/genetic-algorithms/
https://towardsdatascience.com/evolution-of-a-salesman-a-complete-genetic-algorithm-tutorial-for-python-6fe5d2b3ca35

  • 32
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值