利用Java代码实现用遗传算法(EA/GA)求cos(x)在 0~Π上的最大值(最小值只需要该适应度函数即可)
com.tylg.gascosx包下代码
gaMain.java主方法入口
package com.tylg.gascosx;
public class Choose {
// 传入一个二进制数组,通过该函数返回一个选择之后的数组
public String[] choose(String[] genes, double ACCURACY,int GROUPSIZE) // 传入一个二进制字符串,传入精度
{
Code code = new Code();
Fitness fi = new Fitness();
int bit = code.weiShu(ACCURACY); // 二进制染色体的位数
// 未经选择的种群(二进制形式)
double fall = 0;
for (String string : genes) {
double v = code.turevalue(string, bit); // 产生的这个二进制数转化为0~3.1415的十进制数
fall += fi.fitness(v); // f表示总适应度大小
}
// 把种群从二进制变为十进制,再计算出各自的适应度值
double[] farr = new double[GROUPSIZE * 2];
for (int i = 0; i < GROUPSIZE * 2; i++) {
double v = code.turevalue(genes[i], bit);
farr[i] = fi.fitness(v); // 适应度数组
}
// 通过下列的for循环,可以得到一个适应度大小从高到低排序的genes染色体数组
for (int i = 0; i < GROUPSIZE * 2 - 1; i++) {
for (int j = i + 1; j < GROUPSIZE * 2; j++) {
if (farr[i] < farr[j]) {
double c1 = farr[i];
farr[i] = farr[j];
farr[j] = c1;
// 让适应度数组的变化同步至二进制字符串染色体的变化
String c2 = genes[i];
genes[i] = genes[j];
genes[j] = c2;
}
}
}
// 计算出每个染色体的相对适应度,即( fc[i] = farr[i] / fall ),且fc数组中存放的相对适应度从高到低排序的值
double[] fc = new double[GROUPSIZE * 2];
for (int i = 0; i < GROUPSIZE * 2; i++) {
fc[i] = farr[i] / fall;
}
// 轮盘赌---- 可得到一组新的newgenes[]染色体数组
String[] newgenes = new String[GROUPSIZE];
for (int i = 0; i < GROUPSIZE; i++) { // 扔骰子次数为GROUPSIZE次
double dice = Math.random();
double chip1 = 0.0, chip2 = fc[0];
for (int j = 0; j < GROUPSIZE * 2; j++) { // 骰子的值dice与从高到底进行排序的适应度值相比较,
if ((chip1 < dice || chip1 == dice) && (dice < chip2)) {
newgenes[i] = genes[j];
break;
} else {
chip1 = chip2;
chip2 += fc[j + 1];
}
}
}
return newgenes;
}
}
Fitness.java为适应度函数的代码
package com.tylg.gascosx;
public class Fitness {
// 计算单个个体的适应度,输入为一个0~3.1415之间的值
public double fitness(double x) {
double f = 0;
f = (Math.cos(x) + 1) / 2;
return f;
}
/*
* // 批量计算数组的适应度 public double[] fitAll(String str[], int GENE) { double []
* fit = new double[str.length]; for(int i = 0;i < str.length; i++) { fit[i]
* = fitSingle(str[i],GENE); } return fit; }
*
* // 适应度最值(返回序号) public int mFitNum(double fit[]) { double m = fit[0]; int
* n = 0; for(int i = 0; i < fit.length; i++) { if(fit[i] > m) { m = fit[i];
* n = i; } } return n; }
*
* // 适应度最值(返回适应度) public double mFitVal(double fit[]) { double m = fit[0];
* for(int i = 0; i < fit.length; i++) { if(fit[i] > m) { m = fit[i]; } }
* return m; }
*/
}
Encoding.java 为初始化染色体的代码,先生成一条染色体,在生成一组染色体。
package com.tylg.gascosx;
public class Encoding {
// 初始化一条染色体,gene表示染色体的长度,即二进制码的位数
public String initSingle(int gene) {
String res = "";
for (int i = 0; i < gene; i++) {
if (Math.random() < 0.5) {
res += 0;
} else {
res += 1;
}
}
return res;
}
// 初始化一组染色体-----(调用初始化一条染色体函数)gene表示染色体的长度,即二进制码的位数,groupsize即为种群个数
public String[] initAll(int gene, int groupsize) {
String[] initall = new String[groupsize];
for (int i = 0; i < groupsize; i++) {
initall[i] = initSingle(gene);
}
return initall; // 该数组当中包含多个字符串,即包含多条(groupsize)染色体
}
}
Code.java为间接二进制编码及其相对应解码的java代码
package com.tylg.gascosx;
import java.lang.Integer;
/*
* 间接二进制码全部为正数,在此定义域为:0~3.1415,码的位数就是m+1
*/
public class Code {
// 码的位数公式,传入的d为精度(例如:0.00001),输出的为二进制码的位数
public int weiShu(double d) {
double x = (3.1415) / d;
int m = 0;
while (true) {
double low = Math.pow(2, m);
double high = Math.pow(2, m + 1);
if ((low < x) && (!(x > high))) {
return m + 1;
} else {
m++;
}
}
}
// 解码公式, 其中s为某一条染色体的二进制编码字符串
public double turevalue(String s, int m) {
double value = 0;
int x = Integer.parseUnsignedInt(s, 2); // 将二进制数的字符串s变为一个2进制的无符号十进制整数
value = (3.1415 / (Math.pow(2, m) - 1)) * x; // 利用一定的比例此,把一个范围很大十进制数转化为一个范围在0~3.1415之间的数
return value; //计算cos函数值的大小就是用value的值
}
}
Choose.java为选择算子
package com.tylg.gascosx;
public class Choose {
// 传入一个二进制数组,通过该函数返回一个选择之后的数组
public String[] choose(String[] genes, double ACCURACY,int GROUPSIZE) // 传入一个二进制字符串,传入精度
{
Code code = new Code();
Fitness fi = new Fitness();
int bit = code.weiShu(ACCURACY); // 二进制染色体的位数
// 未经选择的种群(二进制形式)
double fall = 0;
for (String string : genes) {
double v = code.turevalue(string, bit); // 产生的这个二进制数转化为0~3.1415的十进制数
fall += fi.fitness(v); // f表示总适应度大小
}
// 把种群从二进制变为十进制,再计算出各自的适应度值
double[] farr = new double[GROUPSIZE * 2];
for (int i = 0; i < GROUPSIZE * 2; i++) {
double v = code.turevalue(genes[i], bit);
farr[i] = fi.fitness(v); // 适应度数组
}
// 通过下列的for循环,可以得到一个适应度大小从高到低排序的genes染色体数组
for (int i = 0; i < GROUPSIZE * 2 - 1; i++) {
for (int j = i + 1; j < GROUPSIZE * 2; j++) {
if (farr[i] < farr[j]) {
double c1 = farr[i];
farr[i] = farr[j];
farr[j] = c1;
// 让适应度数组的变化同步至二进制字符串染色体的变化
String c2 = genes[i];
genes[i] = genes[j];
genes[j] = c2;
}
}
}
// 计算出每个染色体的相对适应度,即( fc[i] = farr[i] / fall ),且fc数组中存放的相对适应度从高到低排序的值
double[] fc = new double[GROUPSIZE * 2];
for (int i = 0; i < GROUPSIZE * 2; i++) {
fc[i] = farr[i] / fall;
}
// 轮盘赌---- 可得到一组新的newgenes[]染色体数组
String[] newgenes = new String[GROUPSIZE];
for (int i = 0; i < GROUPSIZE; i++) { // 扔骰子次数为GROUPSIZE次
double dice = Math.random();
double chip1 = 0.0, chip2 = fc[0];
for (int j = 0; j < GROUPSIZE * 2; j++) { // 骰子的值dice与从高到底进行排序的适应度值相比较,
if ((chip1 < dice || chip1 == dice) && (dice < chip2)) {
newgenes[i] = genes[j];
break;
} else {
chip1 = chip2;
chip2 += fc[j + 1];
}
}
}
return newgenes;
}
}
Cross.java为交叉算子
package com.tylg.gascosx;
/*
* step1:先通过遍历数组的方式,在每次遍历中用随机函数生成一个0~1范围大小的数,再判断交叉概率值cp与其的大小。
* step2:若被选中则与其另外一染色体进行中点交叉,适用字符串交换方法
* step3:把交叉后的数组与原来传入的数组genes数组进行适应度比较,选择其中适应度排名前GROUPSIZE个数组,作为新的染色体
* 以便执行之后的变异操作
*
*/
public class Cross {
/*private static final int GROUPSIZE = 10;
public final static double ACCURACY = 0.0001; // 十进制的精准度(小数点后几位)
Code code = new Code();
int bit = code.weiShu(ACCURACY); // 二进制染色体的位数*/
Code code = new Code();
Fitness fitness = new Fitness();
public String[] cross(String[] genes, int GROUPSIZE, double ACCURACY) {
int bit = code.weiShu(ACCURACY); // 二进制染色体的位数
String[] genes1 = new String[GROUPSIZE];
String[] genes2 = new String[GROUPSIZE];
int c1 = 0, c2 = 0;
for (int i = 0; i < GROUPSIZE; i++) { // for循环就是让genes1与genes2中各分得若干个基因,但其基因总和数为GROUPSIZE个
double x = Math.random(); // c1 , c2中记录的值就是genes1 与 genes2各自的实际长度
if (x < 0.5) {
genes1[c1] = genes[i];
c1++;
} else {
genes2[c2] = genes[i];
c2++;
}
}
// 对genes1或者genes2中的基因进行交叉操作
if (c1 % 2 == 0) { // if else语句就是选择一个染色体数组内染色体个数为一半染色体长度(或者一般少一个)的进行交叉~~~该位置的代码有待优化
int x1 = c1;
for (int i = 0; i < (x1 / 2); i++) {
genes1[i] = genes1[i].substring(0, bit / 2)
+ genes1[x1 - 1 - i].substring(bit / 2, bit);
genes1[x1 - 1 - i] = genes1[i].substring(bit / 2, bit)
+ genes1[x1 - 1 - i].substring(0, bit / 2);
}
} else {
int x1 = c2;
for (int i = 0; i < (x1 / 2); i++) {
genes2[i] = genes2[i].substring(0, bit / 2)
+ genes2[x1 - 1 - i].substring(bit / 2, bit);
genes2[x1 - 1 - i] = genes2[i].substring(bit / 2, bit)
+ genes2[x1 - 1 - i].substring(0, bit / 2);
}
}
String[] genes12 = new String[GROUPSIZE]; //新串genes12就是把经过交叉操作后的genes1与genes2连接在一块
for (int i = 0; i < c1; i++) {
genes12[i] = genes1[i];
}
for (int j1 = 0; j1 < c2; j1++) {
genes12[j1 + c1] = genes2[j1];
}
/*
* 到目前为止,新的基因数组已经产生,他们是genes1与genes2的组合genes12,总数为GROUPSIZE个,
* 需要和他们的父本母本的染色体genes一起进行一个总基因为GROUPSIZE*2个的排序,排序的原则还是按照各自的适应度大小进行排序
*/
String[] tgenes = new String[GROUPSIZE * 2];
for (int i1 = 0; i1 < GROUPSIZE; i1++) {
tgenes[i1] = genes[i1];
}
for (int j1 = 0; j1 < GROUPSIZE; j1++){
tgenes[j1 + GROUPSIZE] = genes12[j1];
}
/*for (String string : tgenes) {
System.out.println("~~~~~~~~~~~~~~~~~~");
System.out.println(string);
} */
// 对新的染色体数组tgenes进行排序,排序规则就是适应度大的排在前面,适应度小的排在后面
Code code = new Code();
for (int m = 0; m < GROUPSIZE * 2 - 1; m++) {
for (int n = m + 1; n < GROUPSIZE * 2; n++) {
if (fitness.fitness(code.turevalue(tgenes[m], bit)) < fitness.fitness(code.turevalue(tgenes[n], bit))) {
String ch = tgenes[m];
tgenes[m] = tgenes[n];
tgenes[n] = ch;
}
}
}
String[] newgenes2 = new String[GROUPSIZE * 2];
for (int j1 = 0; j1 < GROUPSIZE * 2; j1++) {
newgenes2[j1] = tgenes[j1];
}
return newgenes2;
}
}
Mutation.java为变异算子
package com.tylg.gascosx;
public class Mutation {
// private String str;
public String[] mutation(String[] genes, double MP, int GROUPSIZE, double d) {
/*
* 对染色体进行变异操作,步骤如下:
* (1)利用random函数随机生成一个数,利用该数与变异概率进行比较,若该数小于或者等于MP,则选择这个基因作为变异基因
* (2)对被选择的这个变异基因进行变异操作,操作手法为利用random函数随机生成一个 (1~基因长度)数,并对该位进行取反操作
* (3)最后返回该变异过后的数组
*/
Code code = new Code();
int lensingle = code.weiShu(d); // 单个染色体的长度为lensingle
for (int i = 0; i < GROUPSIZE * 2; i++) {
double x = Math.random();
if (x < MP || x == MP) {
String str = genes[i];
int c = (int) (lensingle * Math.random()); // 随机获得一个染色体上的某一位
// 对该位上的0或1进行取反操作
String newstr = "";
if (str.charAt(c) == '0') {
// String newstr = ""; //该newstr中存放的是进行过变异操作后的那个基因
for (int j = 0; j < lensingle; j++) {
if (j == c) {
newstr += '1';
} else {
newstr += str.charAt(j);
}
}
} else {
newstr = "";
c = (int) (lensingle * Math.random());
for (int j1 = 0; j1 < lensingle; j1++) {
if (j1 == c) {
newstr += '0';
} else {
newstr += str.charAt(j1);
}
}
// 现将newstr 替换掉原染色体字符数组中本该进行变异那个基因字符串
genes[i] = newstr;
}
}
}
return genes;
}
}
代码中有些地方可以进行改进,该程序可以根据输入的相应常量值,如(种群数目,迭代次数,十进制精度:用于改变染色体的长度,变异概率)对整个遗传算法进行调整;目前该遗传算法的交叉概率不可调,默认为中点交叉
选择算子为轮盘赌的方式,变异为对染色体上的某一位进行变异,没有采用精英策略
以下是进化算法的普遍设置和现象:
- 1.种群规模20-100个,进化代数2000-3000代,交叉概率0.6-0.8, 变异概率0.01-0.05;
- 2.没有精英策略的EA不能保证在有限代数的进化过程中收敛到最优解;加入精英策略的EA可以保证精英解是最优解,但是种群不收敛;
- 3.进化计算的结果一般是30次独立运行结果的均值。
需注意! EA的两个最严重的问题: 1.不收敛,2. 早熟收敛。