遺伝的アルゴリズム (GA) で 巡回セールスマン問題 (TSP) を解くアニメーション
int n = 25; // 都市の数
int m = 6; // 遺伝子の数
City x[] = new City[n]; // 都市
IntList[] d = new IntList[m]; // 遺伝子(DNA)
IntList[] p = new IntList[m]; // 表現型(Phenotype)
FloatList dis = new FloatList(); // 総距離
int w; // width
int T = 1000000;
boolean useGA = true; // GAを使うかどうか (falseの場合は乱数を使う)
void setup() {
size(1000, 300); frameRate(30);
w = width/m;
for (int i=0;i<n;i++) {
// 乱数で都市の座標を決める
x[i] = new City(i, random(10, w-10), random(70, height-10));
}
for (int i=0;i<m;i++) {
d[i] = getInitDNA(); // 初期値(乱数)
}
}
void draw(){
dis.clear();
background(255);
stroke(122);
text("# city = "+n+" step = "+frameCount,10,20);
text("total distance:",10,35);
for (int i=0;i<m;i++) {
pushMatrix();
// 順序表現に変換
p[i] = encodePath(d[i]);
// 総距離を評価
dis.append(eval(d[i]));
translate(w*i,0);
text("["+i + "] "+dis.get(i),10,50);
drawPath(d[i]);
popMatrix();
}
// 各遺伝子を総距離に基いて評価
IntList r = rankList(dis);
// 2番目に良いものを表示(ピンク)
pushMatrix(); translate(w*r.get(1),0);
stroke(255, 150, 200, 200); drawPath(d[r.get(1)]);
popMatrix();
// 1番目に良いものを表示(赤)
pushMatrix(); translate(w*r.get(0),0);
stroke(255, 0, 0, 200); drawPath(d[r.get(0)]);
popMatrix();
// 最も悪いものを表示(青)
pushMatrix(); translate(w*r.get(r.size()-1),0);
stroke(0, 0, 255); drawPath(d[r.get(r.size()-1)]);
popMatrix();
if(useGA == true){ // GAを使う場合
// 最も悪いものを、新しく生まれたものに入れ替える
IntList d_new = decodePath(crossPath(p[r.get(0)], p[r.get(1)]));
d_new = mutatePath(d_new);
d[r.get(r.size()-1)] = d_new;
}else{ // GAを使わない場合、乱数で新しい遺伝子を得る
d[r.get(r.size()-1)] = getInitDNA();
}
if(frameCount > T) noLoop();
}
// 交叉:2つの遺伝子をランダムな位置で交叉させる
IntList crossPath(IntList x, IntList y){
IntList z = new IntList();
int pos = (int)random(1,x.size()-1); // 交叉場所
for(int i=0;i<x.size();i++){
if(i < pos){
z.append(x.get(i));
}else{
z.append(y.get(i));
}
}
return z;
}
// 変異:遺伝子の中でランダムに2点を交換する
IntList mutatePath(IntList x){
int pos1 = (int)random(0, x.size()-1);
int pos2 = (int)random(0, x.size()-1);
int tmp = x.get(pos1);
x.set(pos1, x.get(pos2));
x.set(pos2, tmp);
return x;
}
// 評価:総距離に応じてランキングを付ける
IntList rankList(FloatList x){
FloatList y = x.copy(); y.sort();
IntList z = new IntList();
for(int i=0;i<y.size();i++){
for(int j=0;j<x.size();j++){
if(y.get(i) == x.get(j)){
z.append(j); break;
}
}
}
return z;
}
// パスの総距離を計算する
float eval(IntList d) {
float dist_all = 0;
for (int i=1;i<n;i++) {
dist_all += dist(x[d.get(i-1)].x, x[d.get(i-1)].y, x[d.get(i)].x, x[d.get(i)].y);
}
return dist_all;
}
// パスを描く
void drawPath(IntList d) {
strokeWeight(10);
for (int i=0;i<n;i++) {
x[i].display();
}
strokeWeight(2);
for (int i=1;i<n;i++) {
line(x[d.get(i-1)].x, x[d.get(i-1)].y, x[d.get(i)].x, x[d.get(i)].y);
}
}
// パスを順序表現に符号化する
IntList encodePath(IntList x) {
IntList u = new IntList();
IntList y = new IntList();
for (int i=0;i<x.size();i++) u.append(i);
for (int i=0;i<x.size();i++) {
for (int j=0;j<u.size();j++) {
if(x.get(i) == u.get(j)){
y.append(j); u.remove(j); break;
}
}
}
return y;
}
// パスを順序表現から戻す
IntList decodePath(IntList x) {
IntList u = new IntList();
IntList y = new IntList();
for (int i=0;i<x.size();i++) u.append(i);
for (int i=0;i<x.size();i++) {
y.append(u.get(x.get(i)));
u.remove(x.get(i));
}
return y;
}
// 初期DNAを得る
IntList getInitDNA() {
IntList x = new IntList();
for (int i=0; i<n; i++) x.append(i);
for (int i=n-1; i>0; i--) {
int rd = (int)random(0, i);
// 乱数をインデックスとして利用し、その配列の中の値と配列末尾の値を交換
// ※末尾(乱数のMAX値)はループごとにデクリメント
// 参考: <http://okwave.jp/qa/q7687312.html>
int tmp = x.get(rd);
x.set(rd, x.get(i));
x.set(i, tmp);
}
return x;
}
// 都市を表すオブジェクト
class City {
int order; // 番号
float x;float y; // 座標
City(int order_, float x_, float y_) {
order = order_; x = x_; y = y_;
}
void display() {
point(x, y); fill(0); text(order, x, y);
}
}