遗传算法求解优化问题
优化问题概述:
m a x f ( x 1 , x 2 ) = 21.5 + x 1 sin ( 4 π x 1 ) + x 2 sin ( 20 π x 2 ) maxf(x_1,x_2) = 21.5+x_1\sin(4{\pi}x_1) +x_2\sin(20{\pi}x_2) maxf(x1,x2)=21.5+x1sin(4πx1)+x2sin(20πx2)
− 3.0 ≤ x 1 ≤ 12.1 , 4.1 ≤ x 2 ≤ 5.8 -3.0≤x_1≤12.1, 4.1≤x_2≤5.8 −3.0≤x1≤12.1,4.1≤x2≤5.8
遗传算法主要分为五步:
首先必须了解种群结构:
N: 种群的规模,即个体的数目
L: 每个个体染色体中的基因数目(L=L1+L2)
L1:个体染色体基因编码的前面L1位可用来表示x1
L2:个体染色体基因编码的后L2位可用来表示x1
输入数据:data.txt文件
C++代码自定义类:
类 | 头文件 | 源文件 |
---|---|---|
Ranseti | Ranseti.h | Ranseti.cpp |
X | X.h | X.cpp |
Zhongqun | Zhongqun.h | Zhongqn.cpp |
Zajiaodui | Zajiaodui.h | Zajiaodui.cpp |
最后运行main.cpp即可。
第一步,个体的染色体编码
很明显,优化问题的可能解为实数对:(x1,x2),若设 aj ≤ xj ≤bj , 所要求的的精度为小数点后t位,这要求将区间[aj , bj]划分为至少(bj-aj)10t份,假设表示变量 xj的二进制串的长度用 lj表示,则l j可取为满足下列不等式的最小正整数m:
( b j − a j ) 1 0 t ≤ 2 m − 1 (b_j - a_j)10^t≤2^m-1 (bj−aj)10t≤2m−1
即有 2 l j − 1 − 1 < ( b j − a j ) 1 0 t ≤ 2 l j − 1 2^{l_j-1}-1<(b_j - a_j)10^t\leq2^{l_j}-1 2lj−1−1<(bj−aj)10t≤2lj−1
将xj的二进制表示转换为十进制表示可按照下式进行计算:
x j = a j + d e c i m a l ( s u b s t r i n g j ) ) × b j − a j 2 l j − 1 x_j=a_j+decimal(substring_j))\times\frac{b_j-a_j}{2^{l_j}-1} xj=aj+decimal(substringj))×2lj−1bj−aj
其中,decimal(substringj)表示变量xj的二进制子串substringj对应点额十进制数。
这样一来,我们可以计算出变量x1和x2的二进制位串长度l1和l2后,便可以得到表示问题可能解(x1,x2)的二进制位串的长度 l=l1+l2 =18+15=33.因此,二进制串的前18位表示变量x1,后15位表示变量x2。
例子:
给定如下33位二进制位串:
010001001011010000 ⏟ 前 18 位 111110010100010 ⏟ 后 15 位 ⏞ 33 位 \overbrace{\underbrace{010001001011010000}_{前18位}\underbrace{111110010100010}_{后15位}}^{33位} 前18位
010001001011010000后15位
111110010100010
33位
那么前18位所表示变量x1的值为:
x 1 = − 3.0 + d e c i m a l ( 010001001011010000 ) × 12.1 − ( − 3.0 ) 2 18 − 1 = − 3.0 + 70352 × 15.1 2 18 − 1 = − 3.0 + 4.052426 = 1.052426 x_1=-3.0+decimal(010001001011010000)\times\frac{12.1-(-3.0)}{2^{18}-1}\\ =-3.0+70352\times\frac{15.1}{2^{18}-1}=-3.0+4.052426=1.052426 x1=−3.0+decimal(010001001011010000)×218−112.1−(−3.0)=−3.0+70352×218−115.1=−3.0+4.052426=1.052426
x 2 = 4.1 + d e c i m a l ( 111110010100010 ) × 5.8 − 4.1 2 15 − 1 = 4.1 + 31906 × 1.7 2 15 − 1 = 4.1 + 1.655330 = 5.755330 x_2=4.1+decimal(111110010100010)\times\frac{5.8-4.1}{2^{15}-1}\\ =4.1+31906\times\frac{1.7}{2^{15}-1}=4.1+1.655330=5.755330 x2=4.1+decimal(111110010100010)×215−15.8−4.1=4.1+31906×215−11.7=4.1+1.655330=5.755330
所以,二进制位串(010001001011010000111110010100010)所表示问题的可能解为(x1,x2)=(1.052426,5.755330)
染色体类的头文件:Ranseti.h
// Ranseti.h
#ifndef RANSETI_H
#define RANSETI_H
#include "X.h"
const int L = 33;
const int L1 = 18;// -3.0≤ x1 ≤ 12.1
const int L2 = 15;// 4.1≤ x2 ≤ 5.8
class Ranseti
{
public:
int shiti[L];
Ranseti(); //初始化构造函数
void shuchu();//打印函数
void shuchu1();//第二种打印函数
X f();//由染色体计算X的函数,X包含x1和x2
};
#endif
染色体类的源文件:Ranseti.cpp
// Ranseti.cp
# include "Ranseti.h"
# include <iostream>
using namespace std;
Ranseti::Ranseti()
{
for (int i = 0; i < L; i++)
shiti[i] = 0;
}
void Ranseti::shuchu()
{
for (int i = 0; i < L; i++)
std::cout << shiti[i];
std::cout << endl;
}
void Ranseti::shuchu1()
{
for (int i = 0; i < L; i++)
{
if (i % 4 == 0) std::cout << " ";
std::cout << shiti[i];
}
std::cout << endl;
}
X Ranseti::f()
{
X Xgeti;
for (int j = 0; j < L1; j++)
Xgeti.x[0] += shiti[j] * pow(2, L1 - 1 - j);
Xgeti.x[0] = -3.0 + Xgeti.x[0] * 15.1 / (pow(2, L1) - 1);//染色体的前L1位二进制对应的变换值
for (int j = L1; j < L; j++)
Xgeti.x[1] += shiti[j] * pow(2, L - 1 - j);
Xgeti.x[1] = 4.1 + Xgeti.x[1] * 1.7 / (pow(2, L2) - 1);//染色体的后L2位二进制对应的变换值
return Xgeti;
}
第二步,产生初始种群
假定初始种群的规模为N=20,随机产生初始种群如下:
v1=(100110100000001111111010011011111),
v2=(111000100100110111001010100011010),
v3=(000010000011001000001010111011101),
v4=(100011000101101001111000001110010),
v5=(000111011001010011010111111000101),
v6=(000101000010010101001010111111011),
v7=(001000100000110101111011011111011),
v8=(100001100001110100010110101100111),
v9=(010000000101100010110000001111100),
v10=(000001111000110000011010000111011),
v11=(011001111110110101100001101111000),
v12=(110100010111101101000101010000000),
v13=(111011111010001000110000001000110),
v14=(010010011000001010100111100101001),
v15=(111011101101110000100011111011110),
v16=(110011110000011111100001101001011),
v17=(011010111111001111010001101111101),
v18=(011101000000001110100111110101101),
v19=(000101010011111111110000110001100),
v2=(101110010110011110011000101111110)
种群类的头文件:Zhongqun.h
// Zhongqun.h
#ifndef ZHONGQUN_H
#define ZHONGQUN_H
#include "Ranseti.h"
#define N 20
class Zhongqun
{
public:
Ranseti r[N];
void shuchu();//打印函数
void shuchu1();//第二种打印方式
};
#endif
种群类的源文件:Zhongqun.cpp
// Zhongqun.cpp
#include "Ranseti.h"
#include "Zhongqun.h"
#include <iostream>
using namespace std;
void Zhongqun::shuchu()
{
for (int i = 0; i < N; i++)
{
r[i].shuchu();
}
}
void Zhongqun::shuchu1()
{
for (int i = 0; i < N; i++)
{
std::cout << "V" << i + 1 << " ";
r[i].shuchu1();
}
}
第三步,计算染色体个体的适应值
(1)首先,需要将二进制位串的染色体转换为所表示问题的可能解(x1,x2)
(2)然后,计算个体x=(x1,x2)的适应值eval(x1,x2)
个体类的头文件:X.h
// X.h
#ifndef X_H
#define X_H
class X
{
public:
double x[2];
X(); //初始化构造函数
double eval(); //根据x1和x2来计算个体适应值的函数
};
#endif
个体类的源文件:X.cpp
// X.cpp
#include "X.h"
#define _USE_MATH_DEFINES //在math.h前定义
#include <math.h>
X::X()
{
for (int i = 0; i < 2; i++)
x[i] = 0;
}
double X::eval()//计算染色体个体适应值的函数
{
return 21.5 + x[0]*sin(4 * M_PI*x[0]) + x[1] *sin(20 * M_PI*x[1]);
}
第四步,父体选择(轮盘赌选择法)
轮盘赌选择(roulette wheel selection)是遗传算法中使用最多的选择策略之一。它通过模拟博彩游戏中的轮盘赌,将一个轮盘划分为N个扇形区域,每个扇形表示种群中的一个染色体,而每个扇形的面积与它所表示的染色体的适应值成正比,如下图所示。为了选择种群中的额个体,设想有一个指针指向轮盘,转动轮盘,当轮盘停止后,指针所指向的染色体被选择。因为一个染色体的适应值越大表示该染色体的扇形面积就越大,因此它被选择的可能性也就越大。
轮盘赌选择可以如下实现:
(1)计算种群中所有染色体适应值之和,
F = ∑ k = 1 N e v a l ( v k ) F = \sum_{k=1}^Neval(v_k) F=k=1∑Neval(vk)
(2)计算每个染色体的选择概率,
p k = e v a l ( v k ) F , k = 1 , 2 , . . . , N p_k = \frac{eval(v_k)}{F},k=1,2,...,N pk=Feval(vk),k=1,2,...,N
(3)计算每个染色体的累计概率,
q k = ∑ j = 1 k p j , k = 1 , 2 , . . . , N q_k = \sum_{j=1}^kp_j,k=1,2,...,N qk=j=1∑kpj,k=1,2,...,N
(4)转动轮盘N次,从中选择N个染色体。
选择过程可如下实现:用[0,1]中的一个随机数r来模拟转动一次轮盘,轮盘停止转动后指针所指向的位置。若r≤q1,这说明指针指向第一个扇形,这时选择第一个染色体v1,一般若qk-1<r≤qk,这说明指针指向第k个扇形,这时选择第k个染色体vk。
第五步,遗传算子
遗传算子有两种:杂交算子和变异算子。
杂交
(1)杂交算子:使用单点杂交,该方法对两个父体进行杂交,杂交后产生两个后代个体。
单点杂交过程如下:设二进制位串的长度为L,首先随机地产生一个整数pos作为杂交点的额位置,pos∈[1,L-1],然后将两个附体在该杂交点右边的子串进行交换,产生两个后代个体。
例如,给定两个父体如下:
v 1 = ( 10011011010010110 ∣ 1000000010111001 ) v_1=(10011011010010110 | 1000000010111001) v1=(10011011010010110∣1000000010111001)
v 2 = ( 00101101010000110 ∣ 0010110011001100 ) v_2=(00101101010000110 | 0010110011001100) v2=(00101101010000110∣0010110011001100)
假设再叫点的位置为17,那么交换两个父体第17个基因右边的子串后,所得到的两个后代如下:
v 1 ′ = ( 10011011010010110 ∣ 0010110011001100 ) v_1'=(10011011010010110 | 0010110011001100) v1′=(10011011010010110∣0010110011001100)
v 2 ′ = ( 00101101010000110 ∣ 1000000010111001 ) v_2'=(00101101010000110 | 1000000010111001) v2′=(00101101010000110∣1000000010111001)
杂交对类的头文件:Zajiaodui.h
// Zajiaodui.h
#ifndef ZAJIAODUI_H
#define ZAJIAODUI_H
#include "Ranseti.h"
class Zajiaodui
{
public:
Ranseti r[2];
Zajiaodui();//构造函数
void shuchu();//打印函数
void shuchu1();//第二种打印函数
};
#endif
杂交对类的源文件:Zajiaodui.cpp
//Zajiaodui.cpp
#include "Zajiaodui.h"
#include "Ranseti.h"
Zajiaodui::Zajiaodui()
{
for (int i = 0; i < L; i++)
{
r[0].shiti[i] = 0;
r[1].shiti[i] = 0;
}
}
void Zajiaodui::shuchu()
{
r[0].shuchu();
r[1].shuchu();
}
void Zajiaodui::shuchu1()
{
r[0].shuchu1();
r[1].shuchu1();
}
变异
(2)变异算子:变异算子的目的在于引入种群中染色体的多样性,防止算法的过早收敛。
变异算子以某一预先指定的概率pm对种群中染色体的每个基因进行变异。当染色体的某一基因被选择进行变异时,若该位位1,则变为0,否则变为1.
例如,给定下列染色体v1:
v 1 = ( 10011011010010110 1 ⏟ 第 18 位 010000010111001 ) v_1 = (10011011010010110\underbrace{1}_{第18位}010000010111001) v1=(10011011010010110第18位
1010000010111001)
若选择v1的第18个基因进行变异,因为该位基因为1,所以将其变为0,得到一个新的染色体v’1,
v 1 ′ = ( 10011011010010110 0 ⏟ 第 18 位 010000010111001 ) v'_1 = (10011011010010110\underbrace{0}_{第18位}010000010111001) v1′=(10011011010010110第18位
0010000010111001)
概率pm是期望改变的种群中染色体的基因个数与基因总数的百分比。本例中,种群中的基因总数为L×N=33×20=660,若pm=0.01,则每一代平均有6.6个基因发生改变。
在遗传算法中应用变异算子过程如下:对种群中的每一个染色体的每一基因,产生一个随机数r,若r<pm,那么该基因进行变异,否则不进行变异。设变异概率pm=0.01,所产生的的660个随机数中有5个比0.01小,假设这5个个体如下表所示。
随机数序号 | 随机数 | 染色体号 | 染色体中基因的位置 |
---|---|---|---|
112 | 0.000213 | 4 | 13 |
349 | 0.009945 | 11 | 19 |
418 | 0.008809 | 13 | 22 |
429 | 0.005425 | 13 | 33 |
602 | 0.002836 | 19 | 8 |
最后
最终的主程序文件为main.cpp
主函数文件:main.cpp
//main.cpp
#include <iostream>
#include <fstream>
#include<string>
#define _USE_MATH_DEFINES //在math.h前定义
#include <math.h>
#include<iomanip>
#include "Ranseti.h"
#include "X.h"
#include "Zajiaodui.h"
#include "Zhongqun.h"
using namespace std;
Zajiaodui Zajiao(Ranseti r1, Ranseti r2, int pos)//根据杂交点位置pos来对两个染色体基因右边的子串进行交换的函数
{
Zajiaodui Rdui;
for (int i = pos; i < L; i++)
{
int temp;
temp = r1.shiti[i];
r1.shiti[i] = r2.shiti[i];
r2.shiti[i] = temp;
}
Rdui.r[0] = r1; Rdui.r[1] = r2;
return Rdui;
}
Zhongqun Bianyi(int randomxuhao[5], Zhongqun zq)//染色体变异函数
{
for (int i = 0; i < 5; i++)
{
int ransetihao = randomxuhao[i] / L, yushu = randomxuhao[i] % L;
if (yushu == 0) zq.r[ransetihao - 1].shiti[L - 1] = 1 - zq.r[ransetihao - 1].shiti[L - 1];
else if (yushu > 0)
{
ransetihao += 1;
zq.r[ransetihao - 1].shiti[yushu - 1] = 1 - zq.r[ransetihao - 1].shiti[yushu - 1];
}
}
return zq;
}
int main()
{
std::cout << "Hello World!" << endl;
//第一步,产生初始种群:N个染色体
Ranseti r[N];//初始化N个染色体
char buf[L + 1];
for (int i = 0; i < L; i++)
buf[i] = 'a';
int j = 0;
ifstream f("E:\\计算智能代码\\data.txt", ios::in);
if (!f.fail())
{
while (!f.eof())
{
f >> buf;
for (int k = 0; k < L; k++)
r[j].shiti[k] = buf[k] - '0';
j++;
}
}
else
cout << "文件不存在" << endl;
f.close();
std::cout << "输入的" << N << "条染色体如下" << endl;
for (int i = 0; i < N; i++)
{
std::cout << i + 1 << " ";
r[i].shuchu1();
}
//第二步,计算染色体个体的适应值
std::cout << "计算适应值如下:" << endl;
double evalvalue[N];//适应值数组
for (int i = 0; i < N; i++)
{
X xgeti = r[i].f();
cout << "eval(v" << (i + 1) << ")f(" << setiosflags(ios::fixed) << setprecision(6) << xgeti.x[0]
<< "," << setiosflags(ios::fixed) << setprecision(6) << xgeti.x[1]
<< ") = " << setiosflags(ios::fixed) << setprecision(6) << xgeti.eval() << endl;
evalvalue[i] = xgeti.eval();
}
//第三步,进行轮盘赌选择来选择父体
double F = 0; //忠犬所有染色体适应值之和
double p[N], q[N];
for (int i = 0; i < N; i++)
{
p[i] = 0; q[i] = 0;
F += evalvalue[i];//计算染色体适应值之和
}
std::cout << "染色体适应值之和为: " << F << endl;
for (int i = 0; i < N; i++)
{
p[i] = evalvalue[i] / F;
}
for (int i = 0; i < N; i++)
{
for (int j = 0; j <= i; j++)
q[i] += p[j];
}
for (int i = 0; i < N; i++)
{
std::cout << "第" << i + 1 << "个染色体的" << "选择概率: " << p[i] << "累计概率: " << q[i] << endl;
}
int rl[N];//选择的N个染色体的上一代索引的数组
double random[N] = {
0.513870,0.175741,0.308652,0.534534,0.947628,
0.171736,0.702231,0.226431,0.494773,0.424720,
0.703899,0.389647,0.277226,0.368071,0.983437,
0.005398,0.765682,0.646473,0.767139,0.780237
};
for (int i = 0; i < N; i++)
{
//轮盘赌选择第i个染色体
j = 0;
while (random[i] > q[j]) j++;//qj<r<+qj+1
rl[i] = j;//选择的第i个染色体对应的上一代的索引
}
for (int i = 0; i < N; i++)
{
std::cout << "选择的第v'" << i + 1 << "个染色体";
for (int j = 0; j < L; j++)
std::cout << r[rl[i]].shiti[j];
std::cout << "(v" << rl[i] + 1 << ")" << endl;
}
Ranseti r_new[N];//父体轮盘赌选择后的新一代染色体
Zhongqun zq_new;
for (int i = 0; i < N; i++)
{
r_new[i] = r[rl[i]];
}
//下面进行遗传,分为杂交和变异
double random1[N] = {
0.822951,0.151932,0.625477,0.314685,0.346901,
0.917204,0.519760,0.401154,0.606758,0.785402,
0.031523,0.869921,0.166525,0.674520,0.758400,
0.581893,0.389248,0.200232,0.355635,0.826927
};
double Pc = 0.25;//,若random1[k]<Pc则选择第k个染色体
int zajiao_ranseti_geshu = 0;
for (int i = 0; i < N; i++)
{
if (random1[i] < Pc)zajiao_ranseti_geshu++;
}//先统计满足条件的杂交染色体个数
cout << "共选出" << zajiao_ranseti_geshu << "个杂交染色体!" << endl;
for (int i = 0; i < N; i++)
{
if (random1[i] < Pc)
{
cout << "选择了" << i + 1<<" ";
r_new[i].shuchu1();
}
}//先统计满足条件的杂交染色体个数
//下面两两配对进行杂交,这里选择pos1=9,2和11染色体杂交,pos2=20让13和18染色体杂交
int pos1 = 9, pos2 = 20;
Zajiaodui z1, z2;
z1 = Zajiao(r_new[1], r_new[10], pos1);
z2 = Zajiao(r_new[12], r_new[17], pos2);
std::cout << "杂交后的结果如下:" << endl;
z1.shuchu1();
z2.shuchu1();
r_new[1] = z1.r[0]; r_new[10] = z1.r[1];
r_new[12] = z2.r[0]; r_new[17] = z2.r[1];
std::cout << "杂交后的种群染色体如下:" << endl;
for (int i = 0; i < N; i++)
{
zq_new.r[i] = r_new[i];
}
zq_new.shuchu1();
//下面进行变异
int Gene_num = L*N;//基因总数
double Pm = 0.01;//变异概率
//这里假设所产生的的660个随机数中有5个比0.01小,进行变异
int randomxuhao[5] = {
112,349,418,429,602};
Zhongqun zq_new1 = Bianyi(randomxuhao, zq_new);
std::cout << "变异后的种群染色体如下:" << endl;
zq_new1.shuchu1();
//最后一步,计算新一代种群染色体个体的适应值
std::cout << "计算适应值如下:" << endl;
double evalvalue_new[N];//适应值数组
for (int i = 0; i < N; i++)
{
X xgeti = zq_new1.r[i].f();
cout << "eval(v" << (i + 1) << ")f(" << setiosflags(ios::fixed) << setprecision(6) << xgeti.x[0]
<< "," << setiosflags(ios::fixed) << setprecision(6) << xgeti.x[1]
<< ") = " << setiosflags(ios::fixed) << setprecision(6) << xgeti.eval() << endl;
evalvalue_new[i] = xgeti.eval();
}
double F_new = 0;
for (int i = 0; i < N; i++)
{
F_new += evalvalue_new[i];
}
std::cout << "上一代种群的染色体适应值之和为: " << F << endl;
std::cout << "新一代种群的染色体适应值之和为: " << F_new << endl;
std::cout << "至此完成了种群的一次演化" << endl;
return 0;
}
运行结果: