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