智能优化算法:遗传算法

1. 引言

遗传算法(Genetic Algorithm, GA)是模拟生物在自然环境中的遗传和进化的过程而形成的自适应全局优化搜索算法。它借用了生物遗传学的观点,通过自然选择、遗传和变异等作用机制,实现各个个体适应性的提高。

1.1 生物学基础

生物遗传和进化的规律:

  • 生物的所有遗传信息都包含在其染色体中,染色体决定了生物的性状。染色体是由基因及其有规律的排列所构成的。
  • 生物的繁殖过程是由其基因的复制过程来完成的。同源染色体的交叉变异会产生新的物种,使生物呈现新的性状。
  • 对环境适应能力强的基因或染色体,比适应能力差的基因或染色体有更多的机会遗传到下一代。

1.2 基本概念

遗传学术语遗传算法术语
群体可行解集
个体可行解
染色体可行解的编码
基因可行解编码的分量
基因形式遗传编码
应适度评价函数值
选择选择操作
交叉交叉操作
变异变异操作

1.2.1 群体和个体

  • 个体就是模拟生物个体而对问题中的对象(一般就是问题的解)的一种称呼,一个个体也就是搜索空间中的一个点。
  • 种群(population)就是模拟生物种群而由若干个体组成的群体, 它一般是整个搜索空间的一个很小的子集。

1.2.2 染色体和基因

  • 染色体(chromosome)就是问题中个体的某种字符串形式的编码表示。
  • 字符串中的字符也就称为基因(gene)。

1.2.3 遗传编码

二进制编码方法是遗传算法中最常用的一种编码方法,它使用的编码符号集是由二进制符号 0 0 0 1 1 1所组成的二值符号集 { 0 , 1 } \{0,1\} {0,1},它所构成的个体基因型是一个二进制编码符号串。

假设某一参数的取值范围是 [ u m i n , u m a x ] [u_{\rm{min}},u_{\rm{max}}] [umin,umax],用长度为 L L L的二进制编码符号来表示该参数。则它总共能够产生 2 L 2^L 2L种不同的编码,参数编码时的对应关系如下:
000 ⋯ 000 = 0 u min ⁡ 000 ⋯ 001 = 1 u min ⁡ + δ ⋯ ⋯ 111 ⋯ 111 = 2 λ − 1 u max ⁡ \begin{aligned} & 000\cdots 000 = 0 & u_{\min} \\ & 000\cdots 001 = 1 & u_{\min}+\delta \\ & \cdots & \cdots \\ & 111\cdots 111 = 2^\lambda-1 & u_{\max} \\ \end{aligned} 000000=0000001=1111111=2λ1uminumin+δumax

二进制编码的精度为:
δ = u max ⁡ − u min ⁡ 2 λ − 1 \delta=\dfrac{u_{\max}-u_{\min}}{2^\lambda -1} δ=2λ1umaxumin

假设某一个体的编码是: x : b λ b λ − 1 b λ − 2 ⋯ b 2 b 1 x:b_\lambda b_{\lambda-1} b_{\lambda-2} \cdots b_{2}b_{1} x:bλbλ1bλ2b2b1,则对应的解码公式为:
x = u min ⁡ + ( ∑ i = 1 λ b ⋅ 2 i − 1 ) δ x = u_{\min}+\left(\sum_{i=1}^{\lambda}{b\cdot 2^{i-1}}\right)\delta x=umin+(i=1λb2i1)δ

1.3 遗传操作

1.3.1 选择操作

选择操作,是从旧的种群中选择适应度高的染色体,放入匹配集(缓冲区),为以后染色体交换、变异,产生新的染色体作准备。

适应度比例法,也叫作轮盘赌、转轮法,按各染色体适应度大小比例来决定其被选择数目的多少。第 i i i个染色体被选的概率为:
P c i = f ( x i ) ∑ j = 1 N p f ( x j ) P_{ci}=\dfrac{f\left(x_i\right)}{\sum_{j=1}^{N_p}{f\left(x_j\right)}} Pci=j=1Npf(xj)f(xi)式中, x i x_i xi为种群中的第 i i i个染色体; f ( x ) f\left(x\right) f(x)为染色体的适应度函数; N p N_p Np为种群大小。

1.3.2 交叉操作

交叉操作,是指对两个相互配对的染色体依据交叉概率 P c P_c Pc按某种方式相互交换其部分基因,从而形成两个新的个体。

具体方法: 随机选择二个染色体(双亲染色体),随机指定一点或多点, ,进行交换,可得二个新的染色体(子辈染色体)。

双亲染色体如下, ∣ {\color{red}|} 为交换点,
A 11010 ∣ 110 B 01011 ∣ 011 \begin{aligned} A\quad & 11010{\color{red}|}110 \\ B\quad & 01011{\color{red}|}011 \\ \end{aligned} AB1101011001011011则通过交叉操作,产生的新的子辈染色体为 A ′ 11010 0 1 1 B ′ 01011 1 1 0 \begin{aligned} A'\quad & 11010{\color{red}0\color{red}1\color{red}1} \\ B'\quad & 01011{\color{red}1\color{red}1\color{red}0} \\ \end{aligned} AB1101001101011110

1.3.3 变异操作

变异操作,即基本位突变算子是指对个体编码串随机指定的某一位或某几位基因作突变运算。

对于基本遗传算法中用二进制编码符号串所表示的个体,若需要进行突变操作的某一基因座上的原有基因值为0,则突变操作将其变为1;反之,若原有基因值为1,则突变操作将其变为0 。

变异产生染色体的多样性,避免进化中早期成熟,陷入局部极值点,突变的概率很低。

父辈染色体如下, ∣ {\color{red}|} 为变异点,
A 1101 ∣ 011 ∣ 0 \begin{aligned} A\quad & 1101{\color{red}|}011{\color{red}|}0 \\ \end{aligned} A11010110则通过变异操作,产生的新的子辈染色体为
A ′ 1101 1 0 0 0 \begin{aligned} A'\quad & 1101{\color{red}1\color{red}0\color{red}0}0 \\ \end{aligned} A11011000

2. 算法内容

2.1 遗传算法流程

  1. 初始化。设置进化代数计数器 g = 0 g=0 g=0,设置最大进化代数 G max ⁡ G_{\max} Gmax,随机生成 N p N_p Np个个体作为初始群体 P ( g ) {\bf{P}}\left(g\right) P(g)
  2. 个体评价。计算群体 P ( g ) {\bf{P}}\left(g\right) P(g)中各个个体的适应度。
  3. 选择操作。将选择算子作用于群体,根据个体适应度,按照一定的规则或方法,选择一些优良个体遗传到下一代群体。
  4. 交叉操作。将交叉算法作用于群体,对选中的成对个体,以某一概率交换它们之间的部分染色体,产生新的个体。
  5. 变异操作。将编译算子作用于群体,对选中的个体,以某一概率改变某一个或某一些基因值为其他的等位基因。
  6. 终止条件判断。若 g ≤ G g\leq G gG,则 g = g + 1 g=g+1 g=g+1,转到步骤2,若 g > G g>G g>G,则进化过程中所得到的具有最大适应度的个体作为最优解输出,终止计算。

2.2 关键参数说明

  • 种群规模 N p N_p Np:群体规模将影响遗传优化的最终结果以及遗传算法的执行效率。一般 N p N_p Np取10到200。

  • 交叉概率 P c P_c Pc:交叉概率 P c P_c Pc控制着交叉操作被使用的频度。一般 P c P_c Pc取0.25到1.00。

  • 变异概率 P m P_m Pm:变异在遗传算法中属于辅助性的搜索操作,它的目的是保持群体的多样性。一般 P m P_m Pm取0.001到0.1。

  • 最大进化代数 G max G_{\text{max}} Gmax:最大进化代数 G max ⁡ G_{\max} Gmax是表示遗传算法运行结束条件的一个参数,表示遗传算法运行到指定代数之后就停止运行。一般 G max ⁡ G_{\max} Gmax取100到1000,视具体情况而定。

3. 代码实现

3.1 Matlab

3.1.1 遗传算法求解函数极值

f ( x ) = x − 10 sin ⁡ ( 5 x ) + 7 cos ⁡ ( 4 x ) f(x)=x-10\sin(5x)+7\cos(4x) f(x)=x10sin(5x)+7cos(4x)的最大值,其中 x x x的范围为 x ∈ [ 0 , 10 ] x\in[0,10] x[0,10]

函数图像和结果:

迭代曲线:

% 遗传算法计算函数最大值
% f(x)=x - 10sin(5x) + 7cos(4x)

% 系统初始化
close all
clear
clc

% 定义相关变量
Np = 32;        % 种群大小
L = 20;         % 编码长度
Pc = 0.8;       % 交叉概率
Pm = 0.1;       % 变异概率
Gmax = 200;     % 迭代次数

Xmin = 0;       % 自变量范围
Xmax = 10;      % 自变量范围

% 种群初始化
pop = randi([0,1],Np,L);

% 定义相关变量
x = zeros(Np,1);        % 记录每一代的变量
trace = zeros(Gmax,1);  % 记录每一代的结果
newPop = zeros(Np,L);   % 记录新种群

% 开始迭代
for k = 1:Gmax
    
    % 将二进制解码为定义域范围内的十进制
    for i = 1:Np
        U = pop(i,:);
        m = 0;
        for j = 1:L
            m = U(j)*2^(j-1)+m;
        end
        x(i) = Xmin + m*(Xmax - Xmin)/(2^L-1);
    end
    Fit = f(x);
    
    [maxFit,rr] = max(Fit);
    minFit = min(Fit);
    fBest = pop(rr,:);
    xBest = x(rr);
    
    Fit = (Fit - minFit)/(maxFit - minFit);
    
    % 基于轮盘赌的选择操作
    sum_Fit = sum(Fit);
    fitvalue = Fit./sum_Fit;
    fitvalue = cumsum(fitvalue);
    ms = sort(rand(Np,1));
    fiti = 1;
    newi = 1;
    while newi <= Np
        if ms(newi)< fitvalue(fiti)
            newPop(newi,:) = pop(fiti,:);
            newi = newi + 1;
        else
            fiti = fiti + 1;
        end
    end
    
    % 基于概率的交叉选择
    for i = 1:2:Np
        p = rand;
        if p < Pc
            q = randi(1,L);
            for j = 1:L
                if q(j) == 1
                    tmp = newPop(i+1,j);
                    newPop(i+1,j) = newPop(i,j);
                    newPop(i,j) = tmp;
                end
            end
        end
    end
    
    % 基于概率的变异操作
    i = 1;
    while i <=round(Np*Pm)
        h = randi([1,Np],1,1);
        for j = 1:round(L*Pm)
            g = randi([1,L],1,1);
            newPop(h,g) =~ newPop(h,g);
        end
        i = i+1;
    end
    
    pop = newPop;
    pop(1,:) = fBest;
    trace(k) = maxFit;
end

xBest;
figure('name','迭代曲线','color','w')
plot(trace,'b','linewidth',1)
set(gca,'FontName','Times New Roman','FontSize',12);
xlabel('迭代次数','FontName','宋体');
ylabel('适应度值','FontName','宋体');
xlim([1,Gmax])

X = linspace(Xmin,Xmax,1000);
Y = f(X);
figure('name','函数图像','color','w')
plot(X,Y,'b','linewidth',1);
hold on
set(gca,'FontName','Times New Roman','FontSize',12);
xlabel('$x$','interpreter','Latex');
ylabel('$f(x)$','interpreter','Latex');
title('$f(x)=x - 10\sin(5x) + 7\cos(4x)$','interpreter','Latex');
ylim([-20,30])
[ymax,xindex] = max(Y);
line1 = plot(X(xindex),ymax,'go');
line2 = plot(xBest,f(xBest),'r*');
hold off
legend([line1,line2],{'Max','GA Result'},'Location','southeast')

function y = f(x)
y = x + 10*sin(5*x) + 7 * cos(4*x);
end

3.1.2 遗传算法求解旅行商问题

使用遗传算法求解旅行商问题,求解路程最短的路径。

求解结果:

迭代曲线:

% 遗传算法求解旅行商问题

% 系统初始化
clear
close all
clc

% 定义起点和终点
R1 = [50;50];
R2 = [500;660];

% 定义途经点
Points = [171.1867  431.8328   346.1714  823.4578 634.4461  381.5585  795.1999 200 620;
    706.0461  276.9230   97.1318   694.8286  438.7444  765.5168  186.8726 500 860];

xy = [R1 Points R2];
N = size(xy,2);

% 计算距离矩阵
dmat = zeros(N,N);      % 定义距离矩阵
for i = 2:N
    for j = 1:i-1
        d = norm(xy(:,i)-xy(:,j));
        dmat(i,j) = d;
        dmat(j,i) = d;
    end
end

popSize = 32;
numIter = 300;

[N,~] = size(dmat);
n = N-2;

% 检测
popSize = 4*ceil(popSize/4);
numIter = max(1,round(real(numIter(1))));


% 初始化种群
pop = zeros(popSize,n);
pop(1,:) = (2:n+1);
for k = 2:popSize
    pop(k,:) = randperm(n)+1;
end

% 遗传算法相关变量
globalMin = Inf;                    % 记录最小值
totalDist = zeros(1,popSize);       % 记录总距离
distHistory = zeros(1,numIter);     % 记录历史值
tmpPop = zeros(4,n);                % 临时种群
newPop = zeros(popSize,n);          % 新种群

% 遗传算法开始迭代
for iter = 1:numIter
    % 评估种群中每一个个体
    for p =1:popSize
        d = dmat(1,pop(p,1));
        for k = 2:n
            d =d+dmat(pop(p,k-1),pop(p,k));
        end
        d = d + dmat(N,pop(p,n));
        totalDist(p) = d;
    end
    
    % 找出最优路径
    [minDist, index] = min(totalDist);
    distHistory(iter) = minDist;
    
    % 替代上一次进化汇总最好的染色体
    globalMinOld = globalMin;
    if(minDist < globalMin)
        globalMin = minDist;
        optRoute = pop(index,:);
    end
    
    % 遗传操作
    randomOrder = randperm(popSize);
    for p = 4:4:popSize
        rtes = pop(randomOrder(p-3:p),:);
        dists = totalDist(randomOrder(p-3:p));
        [~,idx] = min(dists);
        bestOf4Route = rtes(idx,:); %4个里面找一个最好的
        routeInsertionPoints = sort(ceil(n*rand(1,2)));
        I = routeInsertionPoints(1);
        J = routeInsertionPoints(2);
        % 由最好个体变异,得到其他3个个体
        for k = 1:4
            tmpPop(k,:) = bestOf4Route;
            switch k
                case 2 % 翻转
                    tmpPop(k,I:J) = tmpPop(k,J:-1:I);
                case 3 % 交换
                    tmpPop(k,[I J]) = tmpPop(k,[J I]);
                case 4 % 滑移
                    tmpPop(k,I:J) = tmpPop(k,[I+1:J I]);
                otherwise
            end
        end
        newPop(p-3:p,:) = tmpPop;
    end
    pop = newPop;
end

% 最优化路径
optRoute = [1 optRoute N];      % 补全起点和终点

% 绘图:地图
figure('name','地图','color',[1,1,1]);
plot(R1(1),R1(2),'ko');
text(R1(1)+20,R1(2)-20,'S','FontName','Times New Roman','FontSize',12);
hold on
axis equal
axis([0 1000 0 1000]);
plot(R2(1),R2(2),'k*');
text(R2(1)+20,R2(2)-20,'T','FontName','Times New Roman','FontSize',12);
plot(Points(1,:),Points(2,:),'k^');
set(gca,'FontName','Times New Roman','FontSize',12);
xlabel('$x/\rm{m}$','interpreter','Latex');
ylabel('$y/\rm{m}$','interpreter','Latex');
plot(xy(1,optRoute),xy(2,optRoute),'b','linewidth',1)
grid on

% 绘图:迭代曲线
xmax = length(distHistory);
figure('name','迭代曲线','color',[1,1,1]);
plot(distHistory,'b','linewidth',1);
xlim([1,xmax]);
set(gca,'FontName','Times New Roman','FontSize',12);
xlabel('迭代次数','FontName','宋体');
ylabel('$d/\rm{m}$','interpreter','Latex');
grid on

3.2 Python

3.2.1 主文件GA.py

GA.py文件,用到的库matplotlibnumpy ,以及一个数组相关操作的库Array.py

在使用时,注意修改导入Array时的路径!!!

"""
遗传算法

Author  : Xu Zhe
Date    : 2022-03-17
Brief   : 遗传算法基础,遗传算法求解TSP
History : 2022-03-18 : 增加TSP部分
"""
import matplotlib.pyplot as plt
import numpy as np
from C_AlgorithmProgram.C1_DataStructure import Array as Arr


def genetic_algorithm(fun_fitness, x_min, x_max, n_pop, l_ch, p_c, p_m, g_max):
    """
    基础遗传算法

    :param fun_fitness: 适度值函数
    :param x_min: 优化变量范围 最小值
    :param x_max: 优化变量范围 最大值
    :param n_pop: 种群规模
    :param l_ch: 染色体长度
    :param p_c: 交叉概率
    :param p_m: 变异概率
    :param g_max: 最大进化代数
    :return: 最优变量, 最优适度值, 历史记录
    """
    # 种群初始化:随机生成 pop_size 行 ch_length 列的 [0, 1] 矩阵
    pop = np.random.randint(0, 2, (n_pop, l_ch))
    # 记录每一代的所有个体对应的变量
    x_list = np.zeros(n_pop)
    # 记录每一代的所有个体对应的适应度
    fitness_list = np.zeros(n_pop)
    # 记录每一代的最优结果,绘制迭代曲线
    history = np.zeros(g_max)
    # 初始化变量
    x_best = 0
    fitness_max = 0
    # 开始迭代
    for ig in range(g_max):
        # 适度值评估
        for ip1 in range(n_pop):
            # 取出染色体
            chromosome = pop[ip1, :]
            # 解码
            x_list[ip1] = binary_decoding(chromosome, x_min, x_max)
            # 计算种群中每个个体的适应度值
            fitness_list[ip1] = fun_fitness(x_list[ip1])
        # 适度值对大的个体及其索引
        fitness_max = np.max(fitness_list)
        idx = fitness_list.tolist().index(fitness_max)
        # 更新最优个体和最优变量
        ch_best = pop[idx, :]
        x_best = x_list[idx]
        history[ig] = fitness_max
        # 对适度值列表进行归一化
        fitness_min = np.min(fitness_list)
        if fitness_min == fitness_max:
            fitness_list = np.ones(n_pop)
        else:
            fitness_list = (fitness_list - fitness_min) / (fitness_max - fitness_min)

        # 选择操作
        pop = ga_selection(fitness_list, pop, n_pop)
        # 交叉操作
        pop = ga_crossover(pop, n_pop, l_ch, p_c)
        # 变异操作
        pop = ga_crossover(pop, n_pop, l_ch, p_m)
        # 将最优个体放回种群中
        pop[0, :] = ch_best.copy()
    return x_best, fitness_max, history


def ga_selection(fitness_list, pop, n_pop):
    """
    遗传算法选择算子

    :param fitness_list: 适度值列表
    :param pop: 种群
    :param n_pop: 种群大小
    :return: 新的种群
    """
    # 复制一份新种群
    pop_new = pop.copy()
    # 对适度值列表求和
    fitness_sum = np.sum(fitness_list)
    # 归一化
    fitness_norm = fitness_list / fitness_sum
    # 逐项求和,计算概率
    p_list = np.cumsum(fitness_norm)
    # 按该率选择
    for i in range(n_pop):
        # 随机一个概率值
        p = np.random.rand()
        # 找到这个概率值对应的位置
        idx = search_insert(p_list, p)
        # 索引溢出检测
        if idx >= n_pop:
            idx = n_pop - 1
        # 复制选择的个体,生成新的种群
        pop_new[i, :] = (pop[idx, :]).copy()
    return pop_new


def ga_crossover(pop, n_pop, l_ch, p_c):
    """
    遗传算法交叉算子

    :param pop: 种群
    :param n_pop: 种群大小
    :param l_ch: 染色体编码长度
    :param p_c: 交叉概率
    :return: 新的种群
    """
    for i in range(0, n_pop, 2):
        # 随机一个概率值
        p = np.random.rand()
        # 如果满足交叉概率,则进行交叉操作
        if p <= p_c:
            # 确定需要交叉的编码位置
            idx = np.random.randint(0, 2, l_ch)
            for j in range(l_ch):
                # 如果是需要交叉的位置,则进行交换操作
                if idx[j] == 1:
                    tmp = pop[i + 1, j]
                    pop[i + 1, j] = pop[i, j]
                    pop[i, j] = tmp
    return pop


def ga_mutation(pop, n_pop, l_ch, p_m):
    """
    遗传算法变异算子

    :param pop: 种群
    :param n_pop: 种群大小
    :param l_ch: 染色体编码长度
    :param p_m: 变异概率
    :return: 新的种群
    """
    for i in range(n_pop):
        # 随机一个概率值
        p = np.random.rand()
        # 如果满足交叉概率,则进行变异操作
        if p <= p_m:
            # 确定需要变异的编码位置
            idx = np.random.randint(0, 2, l_ch)
            for j in range(l_ch):
                # 如果是需要变异的位置,则进行翻转操作
                if idx[j] == 1:
                    pop[i, j] = 1 - pop[i, j]
    return pop


def search_insert(nums, target):
    """
    二分查找法搜索插入位置

    :param nums:数组
    :param target:目标值
    :return:插入位置或存在位置
    """
    n = len(nums)
    left = 0
    right = n - 1
    ans = n
    while left <= right:
        mid = ((right - left) >> 1) + left
        if target <= nums[mid]:
            ans = mid
            right = mid - 1
        else:
            left = mid + 1
    return ans


def binary_decoding(code, u_min, u_max):
    """
    二进制解码

    :param code: 二进制编码
    :param u_min: 最小值
    :param u_max: 最大值
    :return: 对应的十进制数
    """
    # 编码长度
    lc = len(code)
    # 编码精度
    d = (u_max - u_min) / (2 ** lc - 1)
    x = 0
    # 二进制向十进制转化
    for i in range(lc):
        x += code[i] * (2 ** i)
    # 按照精度和范围,求解对应的十进制数
    return x * d + u_min


# 余胜威. MATLAB优化算法案例分析与应用[M]. 清华大学出版社, 2014:256-258.
# 第17章  基于GA的TSP求解
def genetic_algorithm_tsp(distance_mat, pop_size, g_max):
    """
    遗传算法求解旅行商问题
    :param distance_mat: 距离矩阵
    :param pop_size: 种群大小
    :param g_max: 最大迭代次数
    :return: 序列,距离,历史记录
    """
    # 计算点数
    n_all = np.shape(distance_mat)[0]
    n = n_all - 2
    # 确保种群数时4的倍数,这与下面的遗传操作有关
    pop_size = int(4 * np.ceil(pop_size / 4))
    # 初始化种群
    pop = np.zeros((pop_size, n), dtype=int)
    pop[0, :] = np.arange(1, n + 1)
    for ip3 in range(1, pop_size):
        pop[ip3, :] = np.arange(1, n + 1)
        np.random.shuffle(pop[ip3, :])
    # 初始化相关变量
    route_best = pop[0, :].copy()  # 初始化最优路径
    distance_min = global_min = np.inf  # 初始化最短距离和全局最短
    distance_list = np.zeros(pop_size)  # 用于记录没一次迭代各条路径的距离
    history = np.zeros(g_max)  # 用于记录迭代曲线
    pop_tmp = np.zeros((4, n))  # 用于记录临时4组个体
    pop_new = np.zeros((pop_size, n))
    # 开始迭代
    for ig in range(g_max):
        # 对每一个个体进行评估
        for ip in range(pop_size):
            d = distance_mat[0, int(pop[ip, 0])]
            for k in range(1, n):
                d = d + distance_mat[int(pop[ip, k - 1]), int(pop[ip, k])]
            d = d + distance_mat[n_all - 1, int(pop[ip, n - 1])]
            distance_list[ip] = d
        # 找出最小值
        distance_min, idx = Arr.get_min(distance_list)
        history[ig] = distance_min
        # 更新最优解
        if distance_min < global_min:
            global_min = distance_min
            route_best = pop[idx, :].copy()
        # 遗传操作
        order = np.arange(pop_size)
        np.random.shuffle(order)  # 随机顺序
        for ip2 in range(0, pop_size, 4):
            # 选出四条路径
            routes = pop[order[range(ip2, ip2 + 4)], :]
            # 选出距离
            distance_list_4 = distance_list[order[range(ip2, ip2 + 4)]]
            tmp, idx = Arr.get_min(distance_list_4)
            # 四个里面找出最好的
            route_best_of_4 = routes[idx, :].copy()
            # 设置变换点
            insertion_points = np.sort(np.ceil(n * np.random.random((1, 2)))) - 1
            left = int(insertion_points[0, 0])
            right = int(insertion_points[0, 1])
            for k in range(4):
                pop_tmp[k, :] = route_best_of_4.copy()
                if k == 1:
                    # 翻转操作
                    pop_tmp[k, :] = Arr.flip(pop_tmp[k, :], left, right)
                if k == 2:
                    # 交换操作
                    pop_tmp[k, :] = Arr.swap(pop_tmp[k, :], left, right)
                if k == 3:
                    # 滑移操作
                    pop_tmp[k, :] = Arr.slide(pop_tmp[k, :], left, right)
            # 产生新的种群
            pop_new[range(ip2, ip2 + 4), :] = pop_tmp.copy()
        pop = pop_new.copy()
    return route_best, distance_min, history


def test_function(x):
    """
    优化算法测试函数  f(x) = x + 10 * sin(5 * x) + 7 * cos(4 * x)

    :param x: 自变量
    :return: 因变量
    """
    return x + 10 * np.sin(5 * x) + 7 * np.cos(4 * x)


def ga_function_test():
    x_min = 0
    x_max = 10
    pop_size = 32
    ch_length = 20
    pc = 0.8
    pm = 0.1
    g_max = 200
    x_best, f_best, history = genetic_algorithm(test_function, x_min, x_max, pop_size, ch_length, pc, pm, g_max)
    print(x_best)
    print(f_best)
    x = np.linspace(x_min, x_max, 500)
    y = test_function(x)
    y_max = np.max(y)
    x_idx = y.tolist().index(y_max)
    # plt.style.use('science')

    plt.figure(dpi=120, figsize=(4, 2.5))
    plt.subplots_adjust(left=0.12, right=0.95, top=0.95, bottom=0.18)
    plt.rc('font', family='Times New Roman', size=10)
    plt.rcParams['xtick.direction'] = 'in'
    plt.rcParams['ytick.direction'] = 'in'
    plt.plot(x, y, 'b')
    plt.scatter(x[x_idx], y_max, marker='o', c='w', edgecolors='g', label="Max")
    plt.plot(x_best, f_best, 'r*', label="GA Result")
    plt.xlim((x_min, x_max))
    plt.xlabel("X")
    plt.ylabel("Y")
    plt.legend(fancybox=False, edgecolor='k')
    plt.grid(linestyle="--")

    plt.figure(dpi=120, figsize=(4, 2.5))
    plt.subplots_adjust(left=0.15, right=0.95, top=0.95, bottom=0.18)
    plt.rc('font', family='Times New Roman', size=10)
    plt.rcParams['xtick.direction'] = 'in'
    plt.rcParams['ytick.direction'] = 'in'
    plt.plot(history, 'b')
    plt.xlim((0, g_max))
    plt.xlabel("Index of Iteration")
    plt.ylabel("Fitness")
    plt.grid(linestyle="--")
    plt.show()


def ga_tsp_test():
    # 起始点
    p_s = np.array([50, 50])
    # 终点
    p_t = np.array([950, 950])
    # 途经点
    p_w = np.array([[171.1867, 706.0461],
                    [431.8328, 206.9230],
                    [246.1714, 197.1318],
                    [823.4578, 694.8286],
                    [634.4461, 438.7444],
                    [381.5585, 765.5168],
                    [795.1999, 305.8726],
                    [255.4563, 514.8138],
                    [547.8462, 721.5890]])
    # 合并成所有点
    p_all = np.vstack((p_s, p_w, p_t))
    # 计算所有点的数目
    n_all = np.shape(p_all)[0]
    # 距离矩阵
    distance_mat = np.zeros((n_all, n_all))
    for i in range(1, n_all):
        for j in range(i):
            d = np.linalg.norm(p_all[i, :] - p_all[j, :])
            distance_mat[i, j] = d
            distance_mat[j, i] = d
    # 调用遗传算法求解
    pop_size = 32
    g_max = 300
    route, distance, history = genetic_algorithm_tsp(distance_mat, pop_size, g_max)
    print(distance)
    # 处理路径
    line = p_s
    for i in range(n_all - 2):
        line = np.vstack((line, p_all[int(route[i]), :]))
    line = np.vstack((line, p_t))
    # 绘图
    fig = plt.figure(dpi=120, figsize=(4, 4))
    fig.canvas.manager.set_window_title('计算结果')
    plt.subplots_adjust(left=0.14, right=0.95, top=0.95, bottom=0.18)
    plt.rc('font', family='Times New Roman', size=10)
    plt.rcParams['xtick.direction'] = 'in'
    plt.rcParams['ytick.direction'] = 'in'
    plt.plot(p_s[0], p_s[1], 'g^', label="Start Point")
    plt.plot(p_t[0], p_t[1], 'g*', label="Target Point")
    plt.plot(p_w[:, 0], p_w[:, 1], 'bo', label="Way Points")
    plt.plot(line[:, 0], line[:, 1], 'r-', label="Route")
    plt.axis('equal')
    plt.xlim((0, 1000))
    plt.ylim((0, 1000))
    plt.xlabel("X (m)")
    plt.ylabel("Y (m)")
    plt.grid(linestyle="--")
    plt.legend(fancybox=False, edgecolor='k')
    # 绘制迭代曲线
    fig2 = plt.figure(dpi=120, figsize=(4, 2.5))
    fig2.canvas.manager.set_window_title('迭代曲线')
    plt.subplots_adjust(left=0.14, right=0.95, top=0.95, bottom=0.18)
    plt.rc('font', family='Times New Roman', size=10)
    plt.rcParams['xtick.direction'] = 'in'
    plt.rcParams['ytick.direction'] = 'in'
    plt.plot(history, 'b')
    plt.xlim((0, g_max))
    plt.xlabel("Index of Iteration")
    plt.ylabel("Distance (m)")
    plt.grid(linestyle="--")
    # 显示绘图
    plt.show()


if __name__ == '__main__':
    ga_tsp_test()

3.2.1 数组操作Array.py

"""
数组

Author  : Xu Zhe
Date    : 2022-03-18
Brief   : 数组基本操作
History : 2022-03-18 : 最大值、最小值、翻转、教化、滑移
"""
import numpy as np


def get_min(nums):
    """
    数组最小值及其索引

    :param nums:
    :return:
    """
    n = len(nums)
    res = nums[0]
    idx = 0
    for i in range(n):
        if nums[i] < res:
            res = nums[i]
            idx = i
    return res, idx


def get_max(nums):
    """
    数组最大值及其索引

    :param nums:
    :return:
    """
    n = len(nums)
    res = nums[0]
    idx = 0
    for i in range(n):
        if nums[i] > res:
            res = nums[i]
            idx = i
    return res, idx


def flip(nums, left, right):
    """
    数组元素翻转

    :param nums:
    :param left: 翻转其实为止
    :param right: 翻转结束为止
    :return:
    """
    arr = nums.copy()
    if left >= right:
        return arr
    n = len(arr)
    if right >= n:
        return arr
    tmp = arr[left: right+1].copy()
    tmp = tmp[::-1]
    arr[left: right + 1] = tmp.copy()
    return arr


def swap(nums, left, right):
    """
    数组元素交换

    :param nums: 数组
    :param left: 交换坐标1
    :param right: 交换坐标2
    :return:
    """
    arr = nums.copy()
    n = len(arr)
    if right > n or left > n:
        return arr
    tmp = arr[left]
    arr[left] = arr[right]
    arr[right] = tmp
    return arr


def slide(nums, left, right):
    """
    数组元素滑移

    :param nums: 数组
    :param left: 滑移起始位置
    :param right: 滑移结束位置
    :return: 新数组
    """
    arr = nums.copy()
    if left >= right:
        return arr
    n = len(arr)
    if right > n:
        return arr
    tmp = arr[left]
    for i in range(left, right):
        arr[i] = arr[i + 1]
    arr[right] = tmp
    return arr
  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

亦贤同学

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值