旅行推銷員問題 (TSP) 是經典 NP-Hard 組合優化問題:給定 N 個城市,尋找拜訪每城市恰好一次並返回起點的最短路徑。本文提供完整 Java 遺傳演算法 (GA) 實作,包含初始化、適應度計算、選擇、交叉 (PMX)、變異、精英保留與停止條件。
|i-j| (實務應用真實經緯度/路網距離)private class Genotype {
int[] city; // 城市排列順序 (排列編碼)
long fitness; // 適應度 = 路徑總距離 (越小越好)
double selectP; // 選擇機率
double expectP; // 期望複製數
int isSelected; // 被選中次數 (精英保留用)
}
public Tsp() {
for (int i = 0; i < popSize; i++) {
citys[i] = new Genotype();
int[] pool = IntStream.range(0, cityNum).toArray();
// Fisher-Yates 洗牌
for (int j = 0; j < cityNum; j++) {
int r = j + (int)(Math.random() * (cityNum - j));
citys[i].city[j] = pool[r];
pool[r] = pool[j];
}
}
initDistance(); // 距離矩陣 |i-j|
}
private void calcFitness() {
for (Genotype g : citys) {
g.fitness = 0;
for (int j = 0; j < cityNum - 1; j++)
g.fitness += distance[g.city[j]][g.city[j+1]];
g.fitness += distance[g.city[0]][g.city[cityNum-1]]; // 回起點
}
}
private void calcSelectProb() {
long sum = Arrays.stream(citys).mapToLong(g -> g.fitness).sum();
for (Genotype g : citys) g.selectP = (double)g.fitness / sum; // 越小越好,實務常用 1/fitness
}
private void calcExpectProb() {
for (Genotype g : citys) g.expectP = g.selectP * popSize;
}
private void calcSelected() {
// 整數部分直接複製
int need = popSize;
for (Genotype g : citys) {
int base = (int)g.expectP;
g.isSelected = base;
need -= base;
}
// 小數部分按大小分配剩餘名額
Arrays.stream(citys)
.sorted(Comparator.comparingDouble(g -> g.expectP - (int)g.expectP).reversed())
.limit(need)
.forEach(g -> g.isSelected++);
}
private void pad() {
int bestIdx = 0, badIdx = 0;
while (true) {
while (bestIdx < popSize && citys[bestIdx].isSelected <= 1) bestIdx++;
while (badIdx < popSize && citys[badIdx].isSelected != 0) badIdx++;
if (bestIdx >= popSize || badIdx >= popSize) break;
System.arraycopy(citys[bestIdx].city, 0, citys[badIdx].city, 0, cityNum);
citys[bestIdx].isSelected--;
citys[badIdx].isSelected++;
badIdx++;
}
}
private void crossover() {
int pairs = (int)(popSize * pxover / 2);
for (int k = 0; k < pairs; k++) {
int x = (int)(Math.random() * popSize);
int y = (int)(Math.random() * popSize);
if (x == y) continue;
pmxCrossover(x, y);
}
}
private void pmxCrossover(int x, int y) {
// 1. 找出差異位置
List diffPos = new ArrayList<>();
for (int i = 0; i < cityNum; i++)
if (citys[x].city[i] != citys[y].city[i]) diffPos.add(i);
// 2. 建立映射關係
int[] mapX = new int[cityNum], mapY = new int[cityNum];
for (int pos : diffPos) {
mapX[citys[x].city[pos]] = citys[y].city[pos];
mapY[citys[y].city[pos]] = citys[x].city[pos];
}
// 3. 交換差異段並修正衝突
for (int pos : diffPos) {
int valX = citys[x].city[pos];
int valY = citys[y].city[pos];
citys[x].city[pos] = resolveConflict(valY, mapX);
citys[y].city[pos] = resolveConflict(valX, mapY);
}
}
private int resolveConflict(int val, int[] map) {
while (map[val] != 0) val = map[val]; // 追蹤映射鏈直到無衝突
return val;
}
private void mutate() {
for (Genotype g : citys) {
if (Math.random() < pmutate) {
int i = (int)(Math.random() * cityNum);
int j = (int)(Math.random() * cityNum);
int tmp = g.city[i]; g.city[i] = g.city[j]; g.city[j] = tmp;
}
}
}
public void run() {
long[] recentBest = new long[range];
Arrays.fill(recentBest, -1);
int idx = 0, gen = 1;
while (maxGen-- > 0) {
calcAll(); pad(); crossover(); mutate();
long best = Arrays.stream(citys).mapToLong(g -> g.fitness).min().orElse(Long.MAX_VALUE);
recentBest[idx] = best;
if (isStagnant(recentBest)) break;
idx = (idx + 1) % range;
gen++;
}
printBestRoute();
}
private boolean isStagnant(long[] arr) {
long first = arr[0];
return Arrays.stream(arr).allMatch(v -> v == first && v != -1);
}
----------------- 第 1 代 ------------------------- 最優的解:102 ... ----------------- 第 1523 代 ------------------------- 最優的解:66 最好途徑的序列: 北京 上海 天津 重慶 哈爾濱 長春 沈陽 呼和浩特 石家莊 太原 濟南 鄭州 西安 蘭州 銀川 西寧 烏魯木齊 合肥 南京 杭州 長沙 南昌 武漢 成都 貴州 福建 台北 廣州 海口 南寧 昆明 拉薩 香港 澳門 算法執行時間:12.345 秒
| 模組 | 當前實作 | 改進建議 |
|---|---|---|
| 編碼 | 排列編碼 (Permutation) | 適用 TSP,無需修正 |
| 適應度 | 路徑總距離 (越小越好) | 實務用真實距離矩陣 (Google Maps API / 經緯度) |
| 選擇 | 輪盤法 (適應度比例) | 改用錦標賽選擇,避免過早收斂 |
| 交叉 | PMX (部分映射交叉) | 可嘗試 OX (順序交叉)、CX (循環交叉) |
| 變異 | 雙點交換 | 加入逆序變異、位移變異增加多樣性 |
| 精英策略 | 期望值複製 + 填充 | 固定保留前 N 名精英 (Elitism) |
| 停止條件 | 連續 2000 代最優不變 | 加入時間上限、目標閾值、多樣性指標 |
此實作展示了 GA 解決排列優化問題的完整工程化流程,可作為 TSP、VRP (車輛路徑問題)、排程優化等問題的起始模板。