先修知识
若你对遗传算法的思想不是很熟悉,可以先阅读以下文章,了解什么是遗传算法。
遗传算法图文详解
问题描述
对于文件给出的一个有 500 个节点的图形(n500),利用遗传算法,求最
少可用几种颜色能对其进行着色?并给出对应的着色方案。
问题分析:
题目的目标是用5种颜色,将图1中的每个节点上色,且保证相邻节点间颜色不同;
a) 首先我们依次将图1中的每个节点随机染色,得到一个解序列S;
b) 设置初始种群数量500,随机生成500个解序列,获得初始种群
c) 采取某种选择策略,对初始种群中的解进行选择,遗传到下一代
d) 采取某种交叉策略,对初始种群中的解随机选择进行交叉,遗传到下一代
e) 采取某种变异策略,对初始种群中的解随机选择进行变异,遗传到下一代
f) 结束条件,最优解冲突数为0,或者满足最大循环次数;否则执行c)至e)
伪代码
生成初始种群curGeneration,设下一代种群nextGeneration ← Ø, 并设置迭代次数 NUM,
初始种群数量10,NUM=100
curGeneration←initGeneration, g ←0
while(true){
foreach solution in curGeneration
conflict ←计算solution的冲突值
if conflict==0
成功退出
if g>NUM
失败退出
nextGeneration ← nextGeneration ∪ production(curGeneration)
nextGeneration ← nextGeneration ∪ crossover(curGeneration)
nextGeneration ← nextGeneration ∪ mutation(curGeneration)
curGeneration ← nextGeneration
nextGeneration ← Ø
g ← g+1
局限性
遗传算法作为启发式算法,收敛速度没有退火算法快;其主要是通过选择算子来遗传优良解,交叉算子增加种群的多样性,变异算子对种群中的解进行局部搜索。使用遗传算法求解问题的难点在于如何设定参数,某些情况下可以将遗传算法和退火算法结合,也许会获得较理想的结果。本文用遗传算法求解500个节点的图着色问题,很遗憾,只能用5种颜色染色才能得到优解;
图节点文本文件
K图着色500个节点文件
提取码:o177
具体实现
1.先定义一个存储节点的颜色集合类
package package_java;
import java.util.ArrayList;
public class ColorSet{
public ArrayList node;
public ColorSet(){
node = new ArrayList();
}
public void add(int newNode){
node.add(newNode);
}
public void set(int index, int newNode){
node.set(index,newNode);
}
public int get(int index){
return (int)(node.get(index));
}
public int remove(int index){
return (int)(node.remove(index));
}
public boolean remove(Object o){
return node.remove(o);
}
public int indexOf(int oldNode){
return node.indexOf(oldNode);
}
public int length(){
return node.size();
}
}
2.主类
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import package_java.ColorSet;
public class KGraphColorGA{
/*
*此程序使用遗传算法求解500个节点的图染色问题
*可以用5种颜色求解出最优解
*您可以对Seed:随机数产生用的种子,selectNum:选择数量 crossNum:交叉数量 mutateNum:变异数量进行调整
*看能否是程序收敛更快
*/
/*
*NUM:节点数量 K:颜色数量 P:种群数量 Seed:随机数产生用的种子
*bestConflict:最佳冲突 newSolutionIndex:记录新种群的索引 selectNum:选择数量 crossNum:交叉数量 mutateNum:变异数量
*/
final static int NUM=500,K=5,P=500,Seed=2010;
public static int bestConflict=100, newSolutionIndex=0,selectNum=150,crossNum=100,mutateNum=250;
// 最佳解
public static int[] bestSolution=new int[NUM];
// 存储种群中每个解的冲突
public static int[] conflictsArr=new int[P];
public static void main(String[] args){
int[][] g=new int[NUM][NUM];
// 存储原始种群
int[][] allSolution=new int[P][NUM];
// 存储新种群
int[][] newAllSolution=new int[P][NUM];
String path="D:\\YSA\\Algorithm\\n500.txt";
if(!readNodeFile(g,path)){
System.out.println("fail to read node file !");
}
else
{
// 迭代次数
int steps=40000;
int iteration=0;
// 生成初始种群
initSolution(allSolution,P,NUM,K,Seed);
// 计算初始种群中每个解的冲突
calculateConflictsArr(g,allSolution,NUM,P,K);
// 获取最佳解
getBestSolution(allSolution,NUM,P);
while(iteration<steps&&bestConflict>0){
// 每次循环前置新种群索引为0
newSolutionIndex=0;
// 对原始种群进行选择
selectSolution(allSolution,newAllSolution,NUM,P,selectNum,Seed);
// 对原始种群进行交叉
crossSomeSolution(allSolution,newAllSolution,NUM,P,K,crossNum,Seed);
// 对原始种群进行变异
mutateSolution(allSolution,newAllSolution,NUM,P,K,mutateNum,Seed);
// 将新种群赋给原始种群
newAllSolutionToAllSoliution(allSolution,newAllSolution,NUM,P);
// 计算种群中每个解的冲突
calculateConflictsArr(g,allSolution,NUM,P,K);
// 获取最佳解 注意:如果在第i次种群最优解比第i+1次种群的所有解都要好,则将第i次种群最优解替换第i+1次种群最差解
getBestSolution(allSolution,NUM,P);
if((iteration%400==0)){
System.out.println("the iteration "+iteration+" conflict:"+bestConflict);
}
++iteration;
}
System.out.println("the best solution:");
//printArr(bestSolution,NUM);
ArrayList<ColorSet> colorList=solutionToColorSet(bestSolution,NUM,K);
printNodeSet(K,colorList);
System.out.println();
System.out.println("min conflict:"+bestConflict);
}
}
// 读取节点文件方法
public static boolean readNodeFile(int[][] g, String path){
File file=new File(path);
if(!file.exists()){
return false;
}
ArrayList<String> stringList=new ArrayList<String>();
try{
FileReader fr=new FileReader(file);
BufferedReader br=new BufferedReader(fr);
String str;
while((str=br.readLine())!=null){
stringList.add(str);
}
br.close();
fr.close();
}catch(IOException e){
e.printStackTrace();
}
int m=0,n;
for(int i=1;i<stringList.size();++i){
String[] data=stringList.get(i).split("[ ]");
for(int j=1;j<data.length;++j){
n=Integer.valueOf(data[j]);
g[m][n]=1;
}
++m;
}
return true;
}
// 生成初始种群
public static void initSolution(int[][] allSolution,int p,int n,int K,int seed){
for(int i=0;i<p;++i){
generateOneSolution(allSolution[i],n,K,seed+i);
}
}
// 生成一个初始解
public static void generateOneSolution(int[] colorArr,int n,int K, int seed){
Random rand=new Random();
rand.setSeed(seed);
for(int i=0;i<n;++i){
int k=rand.nextInt(K);
colorArr[i]=k;
}
}
// 将解染色方案生成对应的颜色集合,便于计算解的冲突
public static ArrayList<ColorSet> solutionToColorSet(int[] solution,int n,int K){
ArrayList<ColorSet> colorList=new ArrayList<ColorSet>(K);
for(int i=0;i<K;++i){
ColorSet color=new ColorSet();
colorList.add(color);
}
for(int i=0;i<n;++i){
ColorSet color=colorList.get(solution[i]);
color.add(i);
}
return colorList;
}
// 计算一个解的冲突
public static int calculateSolutionConflicts(int[][] g, int[] solution,int n,int K ){
ArrayList<ColorSet> colorList=solutionToColorSet(solution,n,K);
//printNodeSet(K,colorList);
int conflicts=0;
for(int i=0;i<K;++i){
ColorSet color=colorList.get(i);
for(int j=0;j<color.length()-1;++j){
int row=color.get(j);
for(int k=j+1;k<color.length();++k){
int col=color.get(k);
if(g[row][col]==1){
conflicts +=1;
}
}
}
}
return conflicts;
}
//将某个解赋给最佳解
public static void solutionToBestSoliution(int[] solution,int n){
for(int i=0;i<n;++i){
bestSolution[i]=solution[i];
}
}
// 将新种群赋给原始种群
public static void newAllSolutionToAllSoliution(int[][] allSolution,int[][] newAllSolution,int n,int P){
for(int i=0;i<P;++i){
for(int j=0;j<n;++j){
allSolution[i][j]=newAllSolution[i][j];
}
}
}
// 计算种群中每个解的冲突
public static void calculateConflictsArr(int[][] g,int[][] allSolution,int n,int P,int K){
for(int i=0;i<P;++i){
conflictsArr[i]=calculateSolutionConflicts(g,allSolution[i],NUM,K);
}
}
// 获取最佳解 注意:如果在第i次种群最优解比第i+1次种群的所有解都要好,则将第i次种群最优解替换第i+1次种群最差解
public static void getBestSolution(int[][] allSolution,int n, int P){
// max记录冲突最大的索引
// flag用于标识此种群是否存在比上次种群优秀的解,有为1, 没有为0
int besti=-1, flag=0, max=0;
for(int i=0;i<P;++i){
if(bestConflict>conflictsArr[i]){
bestConflict=conflictsArr[i];
besti=i;
flag=1;
}
if(conflictsArr[max]<conflictsArr[i]){
max=i;
}
}
if(flag==1){
solutionToBestSoliution(allSolution[besti],n);
}
else{
for(int k=0;k<n;++k){
allSolution[max][k]=bestSolution[k];
}
}
}
// 采用轮盘赌算法实现种群的选择
public static void selectSolution(int[][] allSolution,int[][] newAllSolution,int n,int P,int selectNum, int seed){
/*举例:假设有冲突 12,15,16,18
*prob:1/12,1/15,1/16,1/18
*totalProb: 1/12+1/15+1/16+1/18
*accProb: (1/12)/totalProb, (1/12+1/15)/totalProb
*设计accProb的目的是,便于给定一个概率p,比较p与accProb的第i个值,若小于,则选中i
*/
// prob:存储每个解冲突的倒数
double[] prob=new double[P];
// 存储累计概率
double[] accProb=new double[P];
double totalProb=0.0;
for(int i=0;i<P;++i){
prob[i]=1.0/conflictsArr[i];
totalProb +=prob[i];
}
for(int i=0;i<P;++i){
if(i==0){
accProb[i]=prob[i]/totalProb;
continue;
}
accProb[i]=accProb[i-1]+prob[i]/totalProb;
}
Random rand=new Random();
rand.setSeed(seed);
for(int i=0;i<selectNum;++i){
double p=rand.nextDouble();
for(int j=0;j<P;++j){
if(p<accProb[j]){
for(int k=0;k<NUM;++k){
newAllSolution[i][k]=allSolution[j][k];
}
break;
}
}
}
newSolutionIndex=selectNum;
}
// 实现两个解的交叉,
public static void crossTwoSolution(int[] solution1,int [] solution2, int n, int K, int seed){
Random rand=new Random();
rand.setSeed(seed);
int N=rand.nextInt(n);
for(int i=0,j=n-1;i<N;++i,--j){
int temp=solution1[i];
solution1[i]=solution2[j];
solution2[j]=temp;
}
}
// 实现整个种群的交叉
public static void crossSomeSolution(int[][] allSolution,int[][] newAllSolution, int n, int P, int K,int crossNunber, int seed){
Random rand=new Random();
rand.setSeed(seed);
for(int i=0;i<crossNunber/2;++i){
int index1=rand.nextInt(P);
int index2=rand.nextInt(P);
while(index1==index2){
index2=rand.nextInt(P);
}
crossTwoSolution(allSolution[index1],allSolution[index2],n,K,seed);
for(int k=0;k<NUM;++k){
newAllSolution[newSolutionIndex][k]=allSolution[index1][k];
newAllSolution[newSolutionIndex+1][k]=allSolution[index2][k];
}
newSolutionIndex +=2;
}
}
// 实现种群的变异
public static void mutateSolution(int[][] allSolution,int[][] newAllSolution,int n,int P, int K, int mutateNum,int seed){
Random rand=new Random();
rand.setSeed(seed);
for(int i=0;i<mutateNum;++i){
int mutateIndex=rand.nextInt(P);
int index=rand.nextInt(n);
int color=rand.nextInt(K);
while(color==allSolution[mutateIndex][index]){
color=rand.nextInt(K);
}
allSolution[mutateIndex][index]=color;
for(int k=0;k<NUM;++k){
newAllSolution[newSolutionIndex][k]=allSolution[mutateIndex][k];
}
newSolutionIndex++;
}
}
public static void printArr(int[] colorArr, int n){
for(int i=0;i<n;++i){
System.out.print(colorArr[i]+",");
if((i+1)%50==0){
System.out.println();
}
}
}
// 输出解的颜色集合
public static void printNodeSet(int K,ArrayList<ColorSet> nodeList){
for(int i=0;i<K;++i){
ColorSet nodeSet=nodeList.get(i);
int num=nodeSet.length();
for(int j=0;j<num;++j){
System.out.print(nodeSet.get(j)+",");
}
System.out.println();
System.out.println();
}
}
}