人工智能经典问题旅行商求解
使用遗传算法求解,算法流程如下:
- 读取所有的城市坐标,记作集合initCitys
- 生成popSize个种群,记为pops
- 取出种群中适应度最高的,记作rank0
- 使用轮盘算法,从rank0中选出eliteSize个个体,生成下一代,记作pops2
- 对pops2进行交叉
- 对pops2进行概率为rate的随机变异
- 如果繁衍次数大于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()
运行结果