遗传算法及其java实现

时间:2022-03-22 11:09:40

基础概念

  1. 种群:生物的进化是以群体形式进行,一定数量的个体组成了群体,群体中个体的数量叫做群体大小;
  2. 个体:种群的内任意一个对象,是群体的基本组成单位;
  3. 基因:基因是串中的元素,基因用于表示个体的特征,也成为遗传因子;
  4. 染色体:一组基因;
  5. 遗传操作:遗传操作包括以下三个基本遗传算子:选择;交叉;变异。
  6. 最优解:局部最优解和全局最优解;
  7. 适应度:各个个体对环境的适应程度叫做适应度。为了体现染色体的适应能力,引入了对问题中的每一个染色体都能进行度量的函数,叫适应度函数。

理论基础

遗传算法的理论是根据达尔文进化论而设计出来的算法,即人类是朝着好的方向(最优解)进化,进化过程中,会自动选择优良基因,淘汰劣等基因。

基本流程

遗传算法及其java实现

遗传算法思想

遗传算法通过将实际问题模拟为一个生物进化的过程,通过选择,交叉以及变异等操作逐步选取出较优解,而淘汰掉较差的解,以寻求出近似最优解。此处的较优与较差均建立在适应度函数的基础上,不同的适应度函数选取,可能会有不同的结果。

编码

编码是应用遗传算法时要解决的首要问题,也是设计遗传算法时的一个关键步骤。编码方法影响到交叉算子、变异算子等遗传算子的运算方法,大很大程度上决定了遗传进化的效率。编码方式主要可以分为三大类:二进制编码法、浮点编码法、符号编码法。

二进制编码

二进制编码是由二进制符号0和1所组成的二值符号集,其编解码的实现方式简单,交叉变异时的操作简便,但是在进行连续函数的优化时,其局部搜索能力较差,而对于高精度问题,当解接近于最优解时,变异操作会映入较大的变化,导致远离最优解。
格雷码是另一种形式的二进制编码,其相邻码元的码距保持为1,能够有效的避免上述二进制编码潜在的问题。它们的转化形式如下。
假设二进制码元为:
B=bmbm1....b2b1
G=gmgm1....g2g1
- 二进制转格雷码
gm=bm,gi=bi+1bi,i=1,2,...m1
- 格雷码转二进制
bm=gm,bi=bi+1gi,i=1,2,...m1

浮点数编码

所谓浮点数编码,是指个体的每个基因值用某一范围内的一个浮点数来表示。在浮点数编码方法中,必须保证基因值在给定的区间限制范围内,遗传算法中所使用的交叉、变异等遗传算子也必须保证其运算结果所产生的新个体的基因值也在这个区间限制范围内。
譬如对于区间为[a,b],精度要求到小数点后6位,因此需要将区间划分为(b-a)*10^6等分,进而确定最终的编码位数;而对于一串二进制串,需要先将其转化为十进制数,进而转化到对应区间的数值。
编码:
B=deci2inary((xa)prec);
译码(先将二进制转换为十进制数):
x=a+x2n1(ba)

符号编码

符号编码法是指个体染色体编码串中的基因值取自一个无数值含义、而只有代码含义的符号集。对于使用符号编码方法的遗传算法,一般需要认真设计交叉、变异等遗传运算的操作方法,以满足问题的各种约束村求,这样才能提高算法的搜索性能。

初始化群体

遗传算法是对群体进行的进化操作,需要给其淮备一些表示起始搜索点的初始群体数据。在代码的实现中,将群体的规模设置为固定值,即群体中个体的数量保持一直,并且每个个体均随机产生。当然也可将群体的规模设置为一随机变量。

适应度函数

适应度函数的选取将关系到最终的优化结果,是算法运行的关键。根据适应度函数对产生的后代进行评分,评分越高的越接近于最优结果。需要根据具体的问题进行恰当的选取。有时候可直接将适应度函数与目标函数进行等同,有时候又需要区分二者。

选择

根据适应度函数的选取,越适应环境的个体其评分越高,其产生的后代也就越多。一种最常用的选择算子就是“比例选择”,也就是轮盘赌算法,某个个体的被选择的概率可表示为:
p(xi)=f(xi)i=1nf(xi)

交叉

交叉算法是使得子代与父辈表现不同特征的主要原因,通过将父辈中某些基因进行交换实现。在使用中交叉算法很多,可以选取任意方式进行,但最常用的是k-opt交叉算法,其中k可以取任意正整数,简称单点交叉、两点交叉等。就编码方式的不同,其交叉原则也有不同。

二进制编码的交叉方式

二进制编码的基因交换过程非常简单,即―随机把其中几个位于同一位置的编码进行交换,以产生新的个体。

浮点数编码的交叉方式

采用浮点数进行编码时,其与二进制交叉不同的是浮点数编码中交叉的是浮点数而不是二进制,而如果对单个浮点数的基因交叉,就会产生不同的基因组。

变异

变异,也称基因突变,是染色体的某一个位点上基因的改变。基因突变使一个基因变成它的等位基因,并且通常会引起一定的表现型变化。对于二进制编码而言,相当于是变异点上的基因取反;而浮点型编码的基因突变过程一般是对原来的浮点数增加或者减少一个小随机数。

java实现

//GeneAlg.java
package self.algorithm;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.Vector;

public class GeneAlg
{

private static float crossPro = 0.9f;
private static float mutationPro = 0.05f;

// 记录每次运行的最高分以及最好成绩
private float minScore = Float.MIN_VALUE;
private float maxScore = 0.0f;
private String worestPop = null;
private String bestPop = null;

private int popSize; // 种群中染色体的数量
/**
* 0代表固定形式,1代表随机形式
*/

private int popGenMethod; // 种群染色体数量的的产生方式
/**
* 0代表全部遗传,1代表精英遗传
*/

private int geneMethod; // 遗传方式
private int geneSize; // 染色体中基因的数量
private Vector<String> entityList = null; // 种群中个体链表
private List<Float> candidate = null; // 存储数据(必须手动添加)
private int num; // 候选集的数量

// 辅助数据
private float lowBound;
private float upBound;
private float precision;
private int codeLen; // 浮点数编码时适用
private int codeMethod;

public GeneAlg(int popSize, int popGenMethod, int geneMethod, int geneSize,
int codeMethod, float low, float up, float prec, float [] data)
{
this.popSize = popSize;
this.popGenMethod = popGenMethod;
this.geneMethod = geneMethod;
this.geneSize = geneSize;
this.setData(data);

this.codeMethod = codeMethod;
this.lowBound = low;
this.upBound = up;
this.precision = prec;

switch (this.codeMethod)
{
case 0:
case 1:
this.codeLen = Utility.calEncodeLen(0, this.num, 1);
break;
case 2:
this.codeLen = Utility.calEncodeLen(this.lowBound, this.upBound, this.precision);
break;
default:
this.codeLen = Utility.calEncodeLen(0, this.num, 1);
break;
}

entityList = new Vector<>(this.popSize);
}

public void setData(float [] data)
{
candidate = new ArrayList<>();
for (float tmp : data)
{
this.candidate.add(Float.valueOf(tmp));
}
this.num = this.candidate.size();
}

public void setCodeMethod(int codeMethod)
{
this.codeMethod = codeMethod;
}

public void setPopGenMethod(int method)
{
this.popGenMethod = method;
}

public Vector<String> getEntity()
{
return this.entityList;
}

public float getMinScore()
{
return this.minScore;
}
public float getMaxScore()
{
return this.maxScore;
}
public String getBestPop()
{
return this.bestPop;
}
public String getWorestPop()
{
return this.worestPop;
}
public int getCodeLen()
{
return this.codeLen;
}

public void setEntity(Vector<String> vec)
{
entityList.clear();
for (String tmp: vec)
{
entityList.add(tmp);
}
vec.clear();
}
/**
* 初始化群体
*/

public void initPop()
{
switch (this.popGenMethod)
{
case 1:
for (int i = 0; i < new Random().nextInt(this.popSize) + 1; ++i)
{
entityList.add(this.initGene());
}
break;
default:
for (int i = 0; i < this.popSize; ++i)
{
entityList.add(this.initGene());
}
break;
}
}

/**
* 初始化个体的基因
* @return
*/

private String initGene()
{
StringBuffer sb = new StringBuffer();
for (int i = 0; i < this.geneSize; ++i)
{
int rand = new Random().nextInt(this.num);
String code = this.encode(this.candidate.get(rand));
sb.append(code);
}
return sb.toString();
}

/**
* @param a
* @param b
* @return 评分
*/

public float [] calScoreTot()
{
float [] result = new float[entityList.size()];
for (int i = 0; i < entityList.size(); ++i)
{
result[i] = calScore(entityList.get(i));
maxScore = 0.0f;
minScore = Float.MIN_VALUE;
if (result[i] > maxScore)
{
maxScore = result[i];
bestPop = entityList.get(i);
}
if (result[i] < minScore)
{
minScore = result[i];
worestPop = entityList.get(i);
}
}
return result;
}
private float calScore(String pop)
{
float [] data = this.decoder(pop);
return this.getFitness(data);
}
// 适应度函数应该确保没有负数
public float getFitness(float [] data)
{
float sum = 0.0f;
int cnt = 0;
while (cnt < data.length)
{
// sum +=(float)(Math.pow(Math.sin(2*data[cnt++]), 2));
// sum = 5.0f - sum;
sum += (float)Math.pow(data[cnt++], 2);
}
return sum;
}

/**
* @param list
* @return 轮盘赌选择结果
*/

public String select(float [] score)
{
float total = Utility.sum(score);
float aveScore = total / (float)score.length;
float tmp = (float)(Math.random() * total);
float base = 0.0f;
for (int i = 0; i < score.length; ++i)
{
base += score[i];
if (tmp < base && (this.geneMethod == 0 ? true : (score[i] > aveScore)))
{
return this.entityList.get(i);
}
}
return null;
}

/**
* @param father
* @param mother
* @param changeNum
* @return 交叉父辈基因后的子代
*/

public String [] cross(String father, String mother, int changeNum)
{
if (father == null || mother == null)
{
return null;
}
String [] child = new String[2];
StringBuffer fsb = new StringBuffer(father);
StringBuffer msb = new StringBuffer(mother);
int len = father.length() > mother.length() ? mother.length() : father.length();
boolean [] sel = new boolean[len]; // 保证每个位置只交换一次
int cnt = 0;
while (cnt++ < changeNum)
{
int tmp = new Random().nextInt(len);
if (!sel[tmp])
{
// 小于交换概率
if (Math.random() < crossPro )
{
fsb.setCharAt(tmp, mother.charAt(tmp));
msb.setCharAt(tmp, father.charAt(tmp));
sel[tmp] = true;
}
}
}
child[0] = fsb.toString();
child[1] = msb.toString();
return child;
}

/**
* @param child
* @param mutationNum
* @return 变异后的子代
*/

public String mutation(String child, int mutationNum)
{
StringBuffer csb = new StringBuffer(child);
switch (this.codeMethod)
{
case 0:
case 1:
for (int i = 0; i < mutationNum; ++i)
{
if (Math.random() < mutationPro * (i + 1)) // 小于变异概率
{
int tmp = new Random().nextInt(child.length());
char ch = child.charAt(tmp);
switch (ch)
{
case '0':
csb.setCharAt(tmp, '1');
break;
case '1':
csb.setCharAt(tmp, '0');
break;
}
}
}
break;
case 2:
for (int i = 0; i < mutationNum; ++i)
{
float [] data = this.decoder(child);
if (Math.random() < mutationPro * (i + 1))
{
int index = new Random().nextInt(data.length);
float rand = (float)(Math.random() * 2.0 - 1.0);
data[index] += rand;
// 避免越界
if (data[index] > this.upBound)
{
data[index] = this.lowBound + (data[index] - this.upBound) % this.upBound;
}
if (data[index] < this.lowBound)
{
data[index] = this.upBound - (this.lowBound - data[index]) % this.lowBound;
}
}
}
break;
}
String result = new String(child);
return result;
}

/**
* @param data
* @return 对个体进行编码
*/

public String encoder(float [] data)
{
int cnt = 0;
StringBuffer sb = new StringBuffer();
while (cnt < data.length)
{
sb.append(this.encode(data[cnt++]));
}
return sb.toString();
}
/**
* @param data
* @return 对个体进行解码
*/

public float [] decoder(String pop)
{
float [] result = new float[pop.length() / this.codeLen];
int cnt = 0;
while (cnt < result.length)
{
result[cnt] = this.decode(pop.substring(this.codeLen * cnt, (this.codeLen) * (cnt + 1)));
++cnt;
}
return result;
}
/**
* @param data
* @return 针对单个基因的编码
*/

public String encode(float data)
{
String str = null;
switch (this.codeMethod)
{
case 0: // 此时采用的是二进制编码
str = Utility.decToBinary(Math.round(data));
break;
case 1: // 此时采用的是格雷编码
str = Utility.decToGray(Math.round(data));
break;
case 2: // 浮点数编码
str = Utility.floatPointEncode(data, this.lowBound, this.upBound, this.precision);
break;
default:
str = Utility.decToBinary(Math.round(data));
break;
}
if (str.length() < this.codeLen)
{
StringBuffer sb = new StringBuffer();
int cnt = 0;
while (cnt++ < this.codeLen - str.length())
{
sb.append('0');
}
str = sb.append(str).toString();
}
return str;
}
/**
* @param str
* @return 针对单个基因的解码
*/

public float decode(String str)
{
switch (this.codeMethod)
{
case 0: // 此时采用的是二进制译码
return Utility.binaryToDeci(str);
case 1: // 此时采用的是格雷译码
return Utility.grayToDeci(str);
case 2: // 浮点数编码
return Utility.floatPointDecode(str, this.lowBound, this.upBound, this.precision);
default:
return Utility.binaryToDeci(str);
}
}
}

class Utility
{
// 二进制编码
public static String binaryToGray(String binary)
{
if (binary == null)
{
return null;
}
StringBuffer sb = new StringBuffer();
sb.append(binary.charAt(0));
int cnt = 0;
while (cnt < binary.length() - 1)
{
sb.append(Integer.parseInt(String.valueOf(binary.charAt(cnt)))
^ Integer.parseInt(String.valueOf(binary.charAt(cnt + 1))));
++cnt;
}
return sb.toString();
}
public static String grayToBinary(String gray)
{
if (gray == null)
{
return null;
}
StringBuffer sb = new StringBuffer();
sb.append(gray.charAt(0));
int cnt = 1;
while (cnt <= gray.length() - 1)
{
char tmp = sb.charAt(sb.length() - 1);
sb.append(Integer.parseInt(String.valueOf(tmp))
^ Integer.valueOf(String.valueOf(gray.charAt(cnt))));
++cnt;
}
return sb.toString();
}
public static String decToBinary(int deci)
{
return Integer.toBinaryString(deci);
}
public static String decToGray(int deci)
{
return Utility.binaryToGray(Utility.decToBinary(deci));
}
public static float binaryToDeci(String binary) throws NumberFormatException
{
return Integer.valueOf(binary, 2).floatValue();
}
public static float grayToDeci(String gray) throws NumberFormatException
{
return Integer.valueOf(Utility.grayToBinary(gray), 10).floatValue();
}

// 关于浮点数编码,暂时只支持2的次幂形式,如何处理非2的次幂形式的数据???
public static int calEncodeLen(float lowBound, float upBound, float precision)
{
int length = 0;
while (Math.pow(2, length) < (upBound - lowBound) * precision)
{
++length;
}
return length;
}
// 浮点数编码
public static String floatPointEncode(float num, float lowBound, float upBound, float precision)
{
int length = calEncodeLen(lowBound, upBound, precision);
String str = Utility.decToBinary((int)Math.floor((num - lowBound) * precision));
if (str.length() < length)
{
int tmp = length - str.length();
StringBuffer sb = new StringBuffer();
while (tmp-- > 0)
{
sb.append("0");
}
str = sb.append(str).toString();
}
else
{
str = str.substring(str.length() - length);
}
return str;
}
// 浮点数译码
/**
* @param str //要求字符串应使用全长
* @param upBound
* @param lowBound
* @param precision
* @return
*/

public static float floatPointDecode(String str, float lowBound,
float upBound, float precision) throws NumberFormatException
{
int length = str.length();
if (length > calEncodeLen(lowBound, upBound, precision)
|| length < calEncodeLen(lowBound, upBound, precision))
{
throw new NumberFormatException();
}
float deci = binaryToDeci(str);
return (float)((deci / (Math.pow(2, length) - 1) * (upBound - lowBound)) + lowBound);
}

// 求和
public static float sum(ArrayList<Float> list)
{
float tmp = 0.0f;
for (Float f : list)
{
tmp += f.floatValue();
}
return tmp;
}
public static float sum(float [] data)
{
float tmp = 0.0f;
for (float f : data)
{
tmp += f;
}
return tmp;
}
// 平均
public static float average(float [] data)
{
return sum(data) / (float)data.length;
}
// 最小数(不影响原始数据)
public static float min(float [] data)
{
float [] tmp = new float[data.length];
System.arraycopy(data, 0, tmp, 0, data.length);
Arrays.sort(tmp);
return tmp[0];
}
// 最大数(不影响原始数据)
public static float max(float [] data)
{
float [] tmp = new float[data.length];
System.arraycopy(data, 0, tmp, 0, data.length);
Arrays.sort(tmp);
return tmp[tmp.length - 1];
}
}
//GAMain.java
package self.calculate;

import java.util.Random;
import java.util.Vector;

import self.algorithm.GeneAlg;

public class GAMain
{

private int maxChangeNum = 2;
private static int maxMutationNum = 2;
private int maxIteration;

private float best = Float.MIN_VALUE;
private String bestPop = null;

private GeneAlg geneAlg = null;

public GAMain(int popSize, int popGenMethod, int geneMethod, int geneSize, int maxIteration,
int codeMethod, float low, float up, float prec, float [] data)
{
// super(popSize, popGenMethod, geneMethod, geneSize, codeMethod, low, up, prec, data);
this.maxIteration = maxIteration;
this.geneAlg = new GeneAlg(popSize, popGenMethod, geneMethod,
geneSize, codeMethod, low, up, prec, data);
maxChangeNum = geneAlg.getCodeLen() / 2;
}

public void calculate()
{
int iter = 0;
geneAlg.initPop();
while (iter++ < this.maxIteration) //迭代次数
{
float [] score = geneAlg.calScoreTot();
float [] pop = geneAlg.decoder(geneAlg.getBestPop());
for (int i = 0; i < pop.length; ++i)
{
System.out.print(pop[i] + " ");
}
System.out.println(geneAlg.getMaxScore());
if (geneAlg.getMaxScore() > best)
{
best = geneAlg.getMaxScore();
bestPop = geneAlg.getBestPop();
}

int cnt = 0;
Vector<String> popChild = new Vector<>();
while (cnt < score.length) // 子代数目
{
String father = geneAlg.select(score);
String mother = geneAlg.select(score);
int changeNum = new Random().nextInt(maxChangeNum) + 1;
String [] child = geneAlg.cross(father, mother, changeNum);
if (child == null)
{
continue;
}
for (String tmp : child)
{
int mutationNum = new Random().nextInt(maxMutationNum) + 1;
String childMutation = geneAlg.mutation(tmp, mutationNum);
popChild.add(childMutation);
}
cnt += child.length;
}
geneAlg.setEntity(popChild);
}
float [] result = geneAlg.decoder(this.bestPop);
System.out.println("最终最好结果...");
System.out.print(best + ": ");
for (int i = 0; i < result.length; ++i)
{
System.out.print(result[i] + " ");
}
}

public static void main(String [] args)
{
// min 3 - sin(ja)^2 - sin(jb)^2
// max f(a,b) = a^2+b^2; a, b 属于{1, 2, 3, 4, 5, 6, 7}
int popSize = 80, popGenMethod = 0, geneMethod = 0, geneSize = 2, codeMethod = 0;
int maxIteration = 20;
float low = 0, up = 6, prec = 1000000;
float [] data = new float[]{1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f};
// float [] data = new float[(int)((up - low) * prec) + 1];
// for (int i = 0; i < data.length; ++i)
// {
// data[i] = low + i * (1.0f / prec);
// }
long startTime = System.currentTimeMillis();
GAMain ga = new GAMain(popSize, popGenMethod, geneMethod,
geneSize, maxIteration, codeMethod, low, up, prec, data);
ga.calculate();
long endTime = System.currentTimeMillis();
System.out.println("总共耗时: " + (endTime - startTime) + "毫秒。");
}
}