遺傳演算法實現旅行商問題
阿新 • • 發佈:2022-05-16
同步:https://zhufn.fun/archives/yichuansuanfa/
我們選擇遺傳演算法的經典案例——旅行商問題來介紹遺傳演算法的具體實現。
旅行商問題
給定一系列城市和每對城市之間的距離,求解訪問每一座城市一次並回到起始城市的最短迴路。
我們將給每個城市設定一個座標,以此來求得每對城市之間的距離。對於圖上問題,可使用Floyd演算法對圖進行處理,以獲得每對城市之間的最短路。
全域性常量、變數定義
const int SIZE = 1000, CNT = 1000;//種群大小和最大代數 using Pos = pair<double, double>;//座標型別 using Rst = vector<int>;//染色體型別 vector<Pos> a;//城市座標陣列 double ans = 1e9;//全域性最優解,初始化為一個不可能達到的大的長度 Rst ans_rst;//該解對應的染色體 double notans = 1e9;//當前種群最優解,(所以不是答案) double geneSum = 0;//當前種群的參與二元錦標賽的所有解的和 int geneCnt = 0;//當前種群參與二元錦標賽的個體數量,好像不會變 //以上兩行用來求種群平均解,用來觀察種群變化
工具函式
//取隨機浮點數,0~1
double randf() {
return static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
}
//兩點距離
double getDis(Pos p1, Pos p2) {
return sqrt(pow(p1.first - p2.first, 2) + pow(p1.second - p2.second, 2));
}
主要過程
int main() { int n; scanf("%d", &n); for (int i = 1; i <= n; ++i) { double tx, ty; scanf("%lf%lf", &tx, &ty); a.push_back(make_pair(tx, ty)); } //以上為資料輸入部分 //整個種群 vector<Rst> pool; //生成隨機染色體 for (int i = 1; i <= SIZE; ++i) pool.push_back(randGene(n)); //進行CNT代繁衍 for (int i = 1; i <= CNT; ++i) { //初始化本輪統計資料 notans = 1e9; geneSum = 0; geneCnt = 0; printf("%d ", i); //適應度陣列 vector<double> score; //新的種群 vector<Rst> new_pool; for (int i = 1; i <= CNT / 2; ++i) { //選擇兩個親代 auto win1 = choose(pool); auto win2 = choose(pool); //20%的概率進行交叉 if (randf() < 0.2) { auto children = oxCross(win1, win2); win1 = children.first; win2 = children.second; } //嘗試變異,1%的概率變異 bianYi(win1); bianYi(win2); //插入新的種群 new_pool.push_back(win1); new_pool.push_back(win2); } //輸出本輪結果 printf("%lf %lf %lf\n", ans, notans, geneSum / geneCnt); //種群改變 pool = new_pool; } //輸出最優解的染色體 for (int v : ans_rst) printf("%d ", v); return 0; }
基因編碼與產生
我們以旅行商走過的順序作為基因的編碼,染色體是長度為城市數量的10進位制序列。
//隨機生成染色體 Rst randGene(int n) { Rst ret; unordered_map<int, bool> mp; for (int i = 1; i <= n; ++i) { int newr = rand() % n; while (mp[newr]) newr = rand() % n; ret.push_back(newr); mp[newr] = true; } return ret; }
適應度評價
取總路程的倒數作為適應度。該方法產生的適應度總和不為1.
//走的總路程
double getValue(Rst &g) {
int len = g.size();
double s = 0;
for (int i = 1; i < len; ++i)
s += getDis(a[g[i - 1]], a[g[i]]);
s += getDis(a[g[0]], a[g[len - 1]]);
if (s < ans) {
ans = s;
ans_rst = g;
}
if (s < notans)
notans = s;
geneSum += s;
geneCnt++;
return s;
}
//適應度
double getShiYingDu(Rst &g) { return 1.0 / getValue(g); }
選擇
我們採用二元錦標賽的方式進行選擇,即每次隨機抽出兩個個體,保留適應度更高的。相應地,選擇多個個體進行比較的方法即為n元錦標賽。
//選擇,二元錦標賽
Rst &choose(vector<Rst> &pool) {
int len = pool.size();
int i = rand() % len;
int j = rand() % len;
int big = getShiYingDu(pool[i]) > getShiYingDu(pool[j]) ? i : j;
return pool[big];
}
交叉
我們使用的交叉方式是一種順序交叉。從個體2中隨機選取一段放在個體1前面,並將其之後的重複基因去掉。再使用另一個個體中的同一段做同樣的操作。
例如1 5 4 3 2和5 3 2 1 4隨機選擇第1到3位得到的子代是2 1 3 5 4和5 4 3 2 1
//順序交叉(Order Crossover)
pair<Rst, Rst> oxCross(Rst &r1, Rst &r2) {
int len = r1.size();
int i = rand() % len, j = i + rand() % (len - i);
Rst s1, s2;
unordered_map<int, bool> mp1, mp2;
for (int p = i; p <= j; ++p) {
s1.push_back(r2[p]);
mp1[r2[p]] = 1;
s2.push_back(r1[p]);
mp2[r1[p]] = 1;
}
for (int p = 0; p < len; ++p) {
if (!mp1[r1[p]]) {
s1.push_back(r1[p]);
mp1[r1[p]] = 1;
}
if (!mp2[r2[p]]) {
s2.push_back(r2[p]);
mp2[r2[p]] = 1;
}
}
return {s1, s2};
}
變異
一定概率隨機選兩個基因進行交換。
//變異
void bianYi(Rst &r) {
double rd = randf();
if (rd < 0.01) {
int len = r.size();
int i = rand() % len;
int j = rand() % len;
int t = r[i];
r[i] = r[j];
r[j] = t;
}
}
執行測試
我們隨機生成了一組測試資料,包含174個城市座標。下圖是進行了1000次繁衍後的最優解(紅)和種群平均解(綠)。
資料:https://gist.github.com/zhufengning/3a617fc3f3765cd192d42fb27ee374d0
完整程式碼
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <unordered_map>
#include <utility>
#include <vector>
using namespace std;
const int SIZE = 1000, CNT = 1000;
using Pos = pair<double, double>;
using Rst = vector<int>;
vector<Pos> a;
double ans = 1e9;
double notans = 1e9;
double geneSum = 0;
int geneCnt = 0;
Rst ans_rst;
//取隨機浮點數,0~1
double randf() {
return static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
}
//隨機生成解
Rst randGene(int n) {
Rst ret;
unordered_map<int, bool> mp;
for (int i = 1; i <= n; ++i) {
int newr = rand() % n;
while (mp[newr])
newr = rand() % n;
ret.push_back(newr);
mp[newr] = true;
}
return ret;
}
//兩點距離
double getDis(Pos p1, Pos p2) {
return sqrt(pow(p1.first - p2.first, 2) + pow(p1.second - p2.second, 2));
}
//走的總路程
double getValue(Rst &g) {
int len = g.size();
double s = 0;
for (int i = 1; i < len; ++i) {
s += getDis(a[g[i - 1]], a[g[i]]);
}
s += getDis(a[g[0]], a[g[len - 1]]);
if (s < ans) {
ans = s;
ans_rst = g;
}
if (s < notans)
notans = s;
geneSum += s;
geneCnt++;
return s;
}
//適應度
double getShiYingDu(Rst &g) { return 1.0 / getValue(g); }
//選擇,二元錦標賽
Rst &choose(vector<Rst> &pool) {
int len = pool.size();
int i = rand() % len;
int j = rand() % len;
int big = getShiYingDu(pool[i]) > getShiYingDu(pool[j]) ? i : j;
return pool[big];
}
//順序交叉(Order Crossover)
pair<Rst, Rst> oxCross(Rst &r1, Rst &r2) {
int len = r1.size();
int i = rand() % len, j = i + rand() % (len - i);
Rst s1, s2;
unordered_map<int, bool> mp1, mp2;
for (int p = i; p <= j; ++p) {
s1.push_back(r2[p]);
mp1[r2[p]] = 1;
s2.push_back(r1[p]);
mp2[r1[p]] = 1;
}
for (int p = 0; p < len; ++p) {
if (!mp1[r1[p]]) {
s1.push_back(r1[p]);
mp1[r1[p]] = 1;
}
if (!mp2[r2[p]]) {
s2.push_back(r2[p]);
mp2[r2[p]] = 1;
}
}
return {s1, s2};
}
//變異
void bianYi(Rst &r) {
double rd = randf();
if (rd < 0.01) {
int len = r.size();
int i = rand() % len;
int j = rand() % len;
int t = r[i];
r[i] = r[j];
r[j] = t;
}
}
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
double tx, ty;
scanf("%lf%lf", &tx, &ty);
a.push_back(make_pair(tx, ty));
}
//以上為資料輸入部分
//整個種群
vector<Rst> pool;
//生成隨機染色體
for (int i = 1; i <= SIZE; ++i)
pool.push_back(randGene(n));
//進行CNT代繁衍
for (int i = 1; i <= CNT; ++i) {
//初始化本輪統計資料
notans = 1e9;
geneSum = 0;
geneCnt = 0;
printf("%d ", i);
//適應度陣列
vector<double> score;
//新的種群
vector<Rst> new_pool;
for (int i = 1; i <= CNT / 2; ++i) {
//選擇兩個親代
auto win1 = choose(pool);
auto win2 = choose(pool);
//20%的概率進行交叉
if (randf() < 0.2) {
auto children = oxCross(win1, win2);
win1 = children.first;
win2 = children.second;
}
//嘗試變異,1%的概率變異
bianYi(win1);
bianYi(win2);
//插入新的種群
new_pool.push_back(win1);
new_pool.push_back(win2);
}
//輸出本輪結果
printf("%lf %lf %lf\n", ans, notans, geneSum / geneCnt);
//種群改變
pool = new_pool;
}
//輸出最優解的染色體
for (int v : ans_rst)
printf("%d ", v);
return 0;
}