遗传算法(GA)学习以及python实现(附C++TSP)

前些日子,我用C++手写代码实现了遗传算法解决TSP问题,C++太过于繁琐而且写的也不是很好,解决了以下问题:

问题:给定平面上20个点的名称与坐标,两个点之间的距离为它们的欧几里得距离。求一条路径,刚好经过每个点1次,使其路径长度最短。
参数设定如下:
种群大小:M=50
最大代数:G=1000
交叉率:pc=1pc=1,交叉率为1能保证种群的充分进化
变异率:pm=0.1pm=0.1,一般而言,变异发生的可能性较小
在该问题中,每一条路径就是所谓的染色体(解的编码),每条路径的长度就是该个体的适应性(路径长度越短,适应性越强)。交叉操作就是选择两条路径,取一个分界点k,将两条路径分别以分界点k分成前后两段,并且将两条路径重新组合得到新的两条路径。这里的交叉操作蕴含了变异操作,但是能够让子代继承父代的优良特性。变异操作也是实现群体多样性的一种手段,也是全局寻优的保证,具体实现为,按照给定的变异率,对选定的变异的个体,随机的选取三个整数u

这里原理就不具体阐述了,GA是一种基于自然选择原理和自然遗传机制的搜索(寻优)算法,它是模拟自然界中的生命进化机制,在人工系统中实现特定目标的优化。遗传算法的实质是通过群体搜索技术,根据适者生存的原则逐代进化,最终得到最优解或准最优解。它必须做以下操作:初始群体的产生、求每一个体的适应度、根据适者生存的原则选择优良个体、被选出的优良个体两两配对,通过随机交叉其染色体的基因并随机变异某些染色体的基因后生成下一代群体,按此方法使群体逐代进化,直到满足进化终止条件。
以下是解决的C++代码:

#include <iostream>
#include <algorithm>
#include <fstream>
#include <vector>
#include <ctime>
#include "cmath"
using namespace std;
const int maxn = 100;
struct Point{
    string name;
    double x,y;
    int i;
};
vector<Point> p;
double d[maxn][maxn];
int n;
double sum = 0;

double dist(Point a, Point b) { //计算两点距离
    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
double get_sum(vector<Point> a){
    double sum = 0;
    for (int i = 1; i < a.size(); i++) {
        sum += d[a[i].i][a[i - 1].i];
    }
    sum += d[a[0].i][a[a.size()-1].i];
    return sum;
}

void init(){
    srand((unsigned)time(NULL)); //设置随机数种子
    ifstream ifs;
    ifs.open("input.txt");
    p.clear();
    ifs>>n;
    for (int i=0; i<n; ++i){
        Point t;
        ifs>>t.name>>t.x>>t.y;
        t.i = i;
        p.push_back(t);
    }
    for(int i=0; i<n; ++i){
        for(int j=i+1;j<n;++j){
            d[i][j] = d[j][i] = dist(p[i], p[j]);
        }
    }
    sum = get_sum(p);
}

void show() { //显示当前结果
    cout << "路径长度: " << sum << endl;
    cout << "路径:";
    for (int i = 0; i < n; i++)
        cout << ' ' << p[i].name;
    puts("");
}
int w = 100;                  //限定种群只能活100个个体
vector<vector<Point>> group; //种群,也就是染色体列表

void Improve_Circle() { //改良圈法得到初始序列
    vector<Point> cur = p;
    for (int t = 0; t < w; t++) {     //重复50次
        for (int i = 0; i < n; i++) { //构造随机顺序
            int j = rand() % n;
            swap(cur[i], cur[j]);
        }
        int flag = 1;
        while (flag) {
            flag = 0;
            //不断选取uv子串,尝试倒置uv子串的顺序后解是否更优,如果更优则变更
            for (int u = 1; u < n - 2; u++) {
                for (int v = u + 1; v < n - 1; v++) {
                    if (d[cur[u].i][cur[v + 1].i] + d[cur[u - 1].i][cur[v].i] <
                        d[cur[u].i][cur[u - 1].i] + d[cur[v].i][cur[v + 1].i]) {
                        for (int k = u; k <= (u + v) / 2; k++) {
                            swap(cur[k], cur[v - (k - u)]);
                            flag = 1;
                        }
                    }
                }
            }
        }
        group.push_back(cur);
        double cur_sum = get_sum(cur);
        if (cur_sum < sum) {
            sum = cur_sum;
            p = cur;
        }
    }
}

vector<int> get_randPerm(int n) { //返回一个随机序列
    vector<int> c;
    for (int i = 0; i < n; i++) {
        c.push_back(i);
    }
    for (int i = 0; i < n; i++) {
        swap(c[i], c[rand() % n]);
    }
    return c;
}

//排序时用到的比较函数
bool cmp(vector<Point> a, vector<Point> b){
    return get_sum(a) < get_sum(b);
}
int gen = 400; //一共进行400代的进化选择
int c[maxn];
double rate = 0.1; //变异率

void genetic_algorithm() { //遗传算法
    vector<vector<Point>> A = group, B, C;
    // A:当前代的种群  B:交配产生的子代  C:变异产生的子代
    for (int t = 0; t < gen; t++) {
        B = A;
        vector<int> c = get_randPerm(A.size());
        for (int i = 0; i + 1 < c.size(); i += 2) {
            int F = rand() % n; //基因划分分界点
            int u=c[i],v=c[i+1];
            for (int j = F; j < n;j++) { //交换随机选的2个个体的基因后半段,也就是交配
                swap(B[u][j], B[v][j]);
            }
            //交换后可能发生冲突,需要解除冲突
            //保留F前面的部分不变,F后面的部分有冲突则交换
            int num1[1000]={0},num2[1000]={0};
            for(int j=0;j<n;j++){
                num1[B[u][j].i]++;
                num2[B[v][j].i]++;
            }
            vector<Point> v1;
            vector<Point> v2;
            for(int j=0;j<n;j++){
                if(num1[B[u][j].i]==2){
                    v1.push_back(B[u][j]);
                }
            }
            for(int j=0;j<n;j++){
                if(num2[B[v][j].i]==2){
                    v2.push_back(B[v][j]);
                }
            }
            int p1=0,p2=0;
            for(int j=F;j<n;j++){
                if(num1[B[u][j].i]==2){
                    B[u][j]=v2[p2++];
                }
                if(num2[B[v][j].i]==2){
                    B[v][j]=v1[p1++];
                }
            }
        }
        C.clear();
        int flag=1;
        for (int i = 0; i < A.size(); i++) {
            if (rand() % 100 >= rate * 100)
                continue;
            //对于变异的个体,取3个点u<v<w,把子串[u,v]插到w后面
            int u, v, w;
            u = rand() % n;
            do {
                v = rand() % n;
            } while (u == v);
            do {
                w = rand() % n;
            } while (w == u || w == v);
            if (u > v)
                swap(u, v);
            if (v > w)
                swap(v, w);
            if (u > v)
                swap(u, v);

            vector<Point> vec;
            for (int j = 0; j < u; j++)
                vec.push_back(A[i][j]);
            for (int j = v; j < w; j++)
                vec.push_back(A[i][j]);
            for (int j = u; j < v; j++)
                vec.push_back(A[i][j]);
            for (int j = w; j < n; j++)
                vec.push_back(A[i][j]);
            C.push_back(vec);
        }
        //合并A,B,C
        for (int i = 0; i < B.size(); i++) {
            A.push_back(B[i]);
        }
        for (int i = 0; i < C.size(); i++) {
            A.push_back(C[i]);
        }
        sort(A.begin(), A.end(), cmp); //从小到大排序
        vector<vector<Point>> new_A;
        for (int i = 0; i < w; i++) {
            new_A.push_back(A[i]);
        }
        A = new_A;
    }
    group = A;
    sum = get_sum(group[0]);
    p = group[0];
}

int main(){
    init();
    cout << "初始";
    show();
    cout << "改良圈法";
    Improve_Circle();
    show();
    cout << "遗传算法";
    genetic_algorithm();
    show();
    exit(0);
}

在数学建模学习的时候重温GA算法,发现了一位大佬写的非常方便的包scikit-opt

pip install scikit-opt

把源代码中的sko文件夹下载下来放本地也调用

这个包里面有PSO、GA、SA算法调用,这里主要讲GA算法的使用:

遗传算法基本使用并且画图

import numpy as np


def schaffer(p):
    '''
    This function has plenty of local minimum, with strong shocks
    global minimum at (0,0) with value 0
    https://en.wikipedia.org/wiki/Test_functions_for_optimization
    '''
    x1, x2 = p
    part1 = np.square(x1) - np.square(x2)
    part2 = np.square(x1) + np.square(x2)
    return 0.5 + (np.square(np.sin(part1)) - 0.5) / np.square(1 + 0.001 * part2)


# %%
from sko.GA import GA

ga = GA(func=schaffer, n_dim=2, size_pop=50, max_iter=800, prob_mut=0.001, lb=[-1, -1], ub=[1, 1], precision=1e-7)
best_x, best_y = ga.run()
print('best_x:', best_x, '\n', 'best_y:', best_y)
# %% Plot the result
import pandas as pd
import matplotlib.pyplot as plt

Y_history = pd.DataFrame(ga.all_history_Y)
fig, ax = plt.subplots(2, 1)
ax[0].plot(Y_history.index, Y_history.values, '.', color='red')
Y_history.min(axis=1).cummin().plot(kind='line')
plt.show()

也可以使用lambda表达式形式定义变量
在多维优化时,想让哪个变量限制为整数,就设定 precision 为 整数 即可。
例如,我想让我的自定义函数 schaffer 的某些变量限制为整数+浮点数(分别是隔2个,隔1个,浮点数),那么就设定 precision=[2, 1, 1e-7]

from sko.GA import GA

schaffer = lambda x: (x[0] - 1) ** 2 + (x[1] - 0.05) ** 2 + x[2] ** 2
ga = GA(func=schaffer, n_dim=3, max_iter=500, lb=[-1, -1, -1], ub=[5, 1, 1], precision=[2, 1, 1e-7])
best_x, best_y = ga.run()
print('best_x:', best_x, '\n', 'best_y:', best_y)

python遗传算法解决TSP问题

这里使用欧几里得距离(euclidean)求解

import numpy as np
from scipy import spatial
import matplotlib.pyplot as plt

num_points = 50

points_coordinate = np.random.rand(num_points, 2)  # generate coordinate of points
distance_matrix = spatial.distance.cdist(points_coordinate, points_coordinate, metric='euclidean')


def cal_total_distance(routine):
    '''The objective function. input routine, return total distance.
    cal_total_distance(np.arange(num_points))
    '''
    num_points, = routine.shape
    return sum([distance_matrix[routine[i % num_points], routine[(i + 1) % num_points]] for i in range(num_points)])


# %% do GA

from sko.GA import GA_TSP

ga_tsp = GA_TSP(func=cal_total_distance, n_dim=num_points, size_pop=50, max_iter=500, prob_mut=1)
best_points, best_distance = ga_tsp.run()

# %% plot
fig, ax = plt.subplots(1, 2)
best_points_ = np.concatenate([best_points, [best_points[0]]])
best_points_coordinate = points_coordinate[best_points_, :]
ax[0].plot(best_points_coordinate[:, 0], best_points_coordinate[:, 1], 'o-r')
ax[1].plot(ga_tsp.generation_best_Y)
plt.show()

结果如下
在这里插入图片描述

TSP算法限定起始点

固定起点和终点要求路径不闭合(因为如果路径是闭合的,固定与不固定结果实际上是一样的)

假设你的起点和终点坐标指定为(0, 0) 和 (1, 1),这样构建目标函数

  • 起点和终点不参与优化。假设共有n+2个点,优化对象是中间n个点
  • 目标函数(总距离)按实际去写。
import numpy as np
from scipy import spatial
import matplotlib.pyplot as plt

num_points = 20

points_coordinate = np.random.rand(num_points, 2)  # generate coordinate of points
start_point=[[0,0]]
end_point=[[1,1]]
points_coordinate=np.concatenate([points_coordinate,start_point,end_point])
distance_matrix = spatial.distance.cdist(points_coordinate, points_coordinate, metric='euclidean')


def cal_total_distance(routine):
    '''The objective function. input routine, return total distance.
    cal_total_distance(np.arange(num_points))
    '''
    num_points, = routine.shape
    # start_point,end_point 本身不参与优化。给一个固定的值,参与计算总路径
    routine = np.concatenate([[num_points], routine, [num_points+1]])
    return sum([distance_matrix[routine[i], routine[i + 1]] for i in range(num_points+2-1)])

正常运行并画图:

from sko.GA import GA_TSP

ga_tsp = GA_TSP(func=cal_total_distance, n_dim=num_points, size_pop=50, max_iter=500, prob_mut=1)
best_points, best_distance = ga_tsp.run()


fig, ax = plt.subplots(1, 2)
best_points_ = np.concatenate([[num_points],best_points, [num_points+1]])
best_points_coordinate = points_coordinate[best_points_, :]
ax[0].plot(best_points_coordinate[:, 0], best_points_coordinate[:, 1], 'o-r')
ax[1].plot(ga_tsp.generation_best_Y)
plt.show()

结果如下:

在这里插入图片描述

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
以下是Python实现遗传算法解决TSP旅行商问题的示例代码: ```python import random # 城市坐标 city_pos = [(60, 200), (180, 200), (80, 180), (140, 180), (20, 160), (100, 160), (200, 160), (140, 140), (40, 120), (100, 120), (180, 100), (60, 80), (120, 80), (180, 60), (20, 40), (100, 40), (200, 40), (20, 20), (60, 20), (160, 20)] # 种群大小 POP_SIZE = 500 # 城市数量 ITY_COUNT = len(city_pos) # 交叉概率 CROSS_RATE = 0.1 # 变异概率 MUTATION_RATE = 0.02 # 代数 N_GENERATIONS = 500 # 计算两个城市之间的距离 def distance(city1, city2): return ((city1[0] - city2[0]) ** 2 + (city1[1] - city2[1]) ** 2) ** 0.5 # 计算一条路径的总距离 def get_fitness(path): distance_sum = 0 for i in range(CITY_COUNT - 1): distance_sum += distance(city_pos[path[i]], city_pos[path[i+1]]) distance_sum += distance(city_pos[path[-1]], city_pos[path[0]]) return 1 / distance_sum # 初始化种群 def init_population(): population = [] for i in range(POP_SIZE): path = list(range(CITY_COUNT)) random.shuffle(path) population.append(path) return population # 选择 def select(population, fitness): idx = random.randint(0, POP_SIZE - 1) for i in range(POP_SIZE): if random.random() < fitness[i] / fitness.sum(): idx = i break return population[idx] # 交叉 def crossover(parent1, parent2): if random.random() < CROSS_RATE: child = [-1] * CITY_COUNT start = random.randint(0, CITY_COUNT - 1) end = random.randint(start, CITY_COUNT - 1) child[start:end+1] = parent1[start:end+1] for i in range(CITY_COUNT): if parent2[i] not in child: for j in range(CITY_COUNT): if child[j] == -1: child[j] = parent2[i] break return child else: return parent1 # 变异 def mutate(child): if random.random() < MUTATION_RATE: idx1, idx2 = random.sample(range(CITY_COUNT), 2) child[idx1], child[idx2] = child[idx2], child[idx1] return child # 遗传算法主函数 def genetic_algorithm(): population = init_population() for generation in range(N_GENERATIONS): fitness = [get_fitness(path) for path in population] best_path = population[fitness.index(max(fitness))] print("Generation:", generation, "| Best path length:", 1 / max(fitness)) new_population = [best_path] for i in range(POP_SIZE - 1): parent1 = select(population, fitness) parent2 = select(population, fitness) child = crossover(parent1, parent2) child = mutate(child) new_population.append(child) population = new_population return best_path # 运行遗传算法 best_path = genetic_algorithm() print("Best path:", best_path) ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dylan、

耕码不易,白嫖可耻

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值