【人工智能导论】遗传算法求解TSP问题(含源码github)
源程序:Github鏈接
Symmetric traveling salesman problem (TSP)
Given a set of n nodes and distances for each pair of nodes, find a roundtrip of minimal total length visiting each node exactly once. The distance from node i to node j is the same as from node j to node i.
旅行商問題
假設有一個旅行商人要拜訪n個城市,他必須選擇所要走的路徑,路徑的限制是每個城市只能拜訪一次,而且最后要回到原來出發的城市。路徑的選擇目標是要求得的路徑路程為所有路徑之中的最小值。
附:TSPLIB上的旅行商問題標準測試用例
遺傳算法
生物進化與遺傳算法
遺傳算法是從代表問題可能潛在的解集的一個種群(population)開始的,而一個種群則由經過基因(gene)編碼的一定數目的個體(individual)組成。每個個體實際上是染色體(chromosome)帶有特征的實體。染色體作為遺傳物質的主要載體,即多個基因的集合,其內部表現(即基因型)是某種基因組合,它決定了個體的形狀的外部表現。因此,在一開始需要實現從表現型到基因型的映射即編碼工作。如二進制編碼;
初代種群產生之后,按照適者生存和優勝劣汰的原理,逐代(generation)演化產生出越來越好的近似解,在每一代,根據問題域中個體的適應度(fitness)大小選擇(selection)個體,并借助于自然遺傳學的遺傳算子(genetic operators)進行組合交叉(crossover)和變異(mutation),產生出代表新的解集的種群。
這個過程將導致種群像自然進化一樣的新生代種群比前代更加適應于環境,最后一代種群中的最優個體經過解碼(decoding),可以作為問題近似最優解。
1、選擇過程:“輪盤賭”法
設群體的規模為 N ,F(xi)(i=1, …, N)是其中每個染色體的適應值。
(1)r=random(0, 1),s=0,i=0;
(2)如果s≥r,則轉(4);
(3)s=s+p(xi),i=i+1, 轉(2)
(4)xi 即為被選中的染色體,輸出i
(5)結束
2、交配過程:十進制交配。隨機產生多個交配位,對于這些位置上的基因,子代1從父代2中直接得到,子代2從父代1中直接得到,其余位置保持不變。如果產生相同數字沖突,依次從父代中選取不重復的數字,替換掉重復位。
3、變異過程:十進制變異。同一條Dna的變異次數由變異比例決定,可調節。變異時,每次隨機選擇兩個變異位,將兩個位置的數字進行交換。
測試用例與求解結果
改進方向
根據測試,在不同參數組合下,算法收斂的速度不同。在上圖所示參數狀態下,算法性能較高。可進一步對參數的組合方式進行調節,優化算法性能。
關于適應函數的改進:
本算法直接選取問題的指標函數作為適應函數。如,求函數f(x)的最小值,就直接采用M-f(x)(M為自定義常數)為適應函數。
但有些時候,函數f(x)在最大值附近的變化可能會非常小,以至于他們的適應值非常接近,很難區分出那個染色體占優。此時,便需要定義新的適應函數,且要求該適應函數與問題的指標函數具有相同的變化趨勢,但變化的速度更快。因此,可考慮使用線性加速適應函數,或非線性加速適應函數,對本算法進行優化。
求解結果截圖
城市數量為16時,求解結果如下:
源程序
/* ----------------Init.java---------------- */ package cn.hanquan.ai;import cn.hanquan.ai.pojo.CityBoard;/*** 用于初始化的全局變量* @author Buuug**/ public class Init {/*** 城市總數*/public static final int CITYNUM = 16;/*** 種群規模*/public static final int LIVINGSIZE = 200;/*** 迭代次數*/public static final int GENERATION = 2000;/*** 交配概率(1)->(1)+(2)+(3)=1*/public static final double PC = 0.3;/*** 變異概率(2)->(1)+(2)+(3)=1*/public static final double PM = 0.3;/*** 直接保留父代比例(3)->(1)+(2)+(3)=1*/public static final double PS = 0.4;/*** 交配位比例*/public static final double PCRatio = 0.3;/*** 變異位比例*/public static final double PMRatio = 0.1;/*** 直接輸入答案*/public static final Integer[] ANSWERDNA = new Integer[] { 1, 22, 8, 26, 31, 28, 3, 36, 35, 20, 2, 29, 21, 16, 50,34, 30, 9, 49, 10, 39, 33, 45, 15, 44, 42, 40, 19, 41, 13, 25, 14, 24, 43, 7, 23, 48, 6, 27, 51, 46, 12, 47,18, 4, 17, 37, 5, 38, 11, 32 };/*** 城市坐標總圖*/public static CityBoard CITYBOARD;public static double MAXADAPT = 200; }/* ----------------Main.java---------------- */ package cn.hanquan.ai;import java.util.ArrayList;import org.apache.log4j.Logger;import cn.hanquan.ai.pojo.CityBoard; import cn.hanquan.ai.pojo.Dna; import cn.hanquan.ai.pojo.DnaPool; import cn.hanquan.ai.pojo.Node; import cn.hanquan.ai.util.CrossUtil; import cn.hanquan.ai.util.MutateUtil; import cn.hanquan.ai.util.Utils;/*** 遺傳算法求解旅行商問題* (1)給定群體規模 N,交配概率 pc 和變異概率 pm,t=0; (2)隨機生成 N 個染色體作為初始群體; (3)對于群體中的每一個染色體 xi 分別計算其適應值 F(xi); (4)如果算法滿足停止準則,則轉(10); (5)對群體中的每一個染色體 xi 計算概率; (6)依據計算得到的概率值,從群體中隨機地選取 N 個染色體,得到種群; (7)依據交配概率 pc 從種群中選擇染色體進行交配,其子代進入新的群體,種群中未進行交配的染色體,直接復制到新群體中; (8)依據變異概率 pm 從新群體中選擇染色體進行變異,用變異后的染色體代替新群體中的原染色體; (9)用新群體代替舊群體,t=t+1轉(3); (10)進化過程中適應值最大的染色體,經解碼后作為最優解輸出; (11)結束. * * @author luyang.gong**/ public class Main {private static Logger logger = Logger.getLogger(Node.class);public static void main(String[] args) {int saveCount = (int) (Init.LIVINGSIZE * Init.PS);int crossCount = (int) (Init.LIVINGSIZE * Init.PC);int mutateCount = (int) (Init.LIVINGSIZE - saveCount - crossCount);System.out.println("Please input NODE_COORD_SECTION:");Init.CITYBOARD = Utils.readCityBoard();DnaPool dnaPool = new DnaPool(Init.LIVINGSIZE);// Dna dnaAns = Utils.genSolutionDna(); //已知最優解:74.10873595815309 // Dna dnaAns = Utils.genSolutionDna(); //22城市,已知最優解:75.66514947135613 // double answer = Utils.calTotalDistance(dnaAns); // logger.info("answer=" + answer); // return;double minDis = Double.POSITIVE_INFINITY;Dna bestDna = dnaPool.dnaList.get(0);for (int i = 0; i < Init.GENERATION; i++) { // 共進行GENERATION次迭代logger.info("第 " + i + "次迭代");DnaPool newPool = new DnaPool();Utils.updateAdaptValueForAll(dnaPool);Utils.updateProbForAll(dnaPool);//計算當前Dna池中數據for (int j = 0; j < Init.CITYNUM; j++) {// 當前旅行路徑Dna curDna = dnaPool.dnaList.get(j);logger.info("curDna=" + curDna);double dis = Utils.calTotalDistance(curDna);if (dis < minDis) {minDis = dis;bestDna = curDna;logger.info("發生替換,替換后minDis=" + minDis);logger.info("發生替換,替換后bestDna=" + bestDna);}}// 直接進入下一代for (int j = 0; j < saveCount; j++) {Dna randDna = Utils.getRandCycleDna(dnaPool);newPool.dnaList.add(randDna);}// 交配產生的后代for (int j = 0; j < crossCount; j++) {Dna dna1 = Utils.getRandCycleDna(dnaPool);Dna dna2 = Utils.getRandCycleDna(dnaPool);ArrayList<Dna> twoDna = CrossUtil.doCross(dna1, dna2);newPool.dnaList.add(twoDna.get(0));newPool.dnaList.add(twoDna.get(1));}// 變異產生的后代for (int j = 0; j < mutateCount; j++) {Dna dna = Utils.getRandCycleDna(dnaPool);Dna newDna = MutateUtil.doMutate(dna);newPool.dnaList.add(newDna);}dnaPool = newPool;logger.info("當前迭代minDis=" + minDis);logger.info("當前迭代bestDna=" + bestDna);}logger.info("最終minDis=" + minDis);logger.info("最終bestDna=" + bestDna);} }/* ----------------CityBoard.java---------------- */ package cn.hanquan.ai.pojo;import java.util.HashMap;import org.apache.log4j.Logger;/*** 城市坐標*/ public class CityBoard {private static Logger logger = Logger.getLogger(CityBoard.class);/*** 需要走過的城市坐標: 0,不需要經過 1,需要經過*/public int[][] arr;public HashMap<Integer, Node> allCityMap;public CityBoard() {this.allCityMap = new HashMap();}@Overridepublic String toString() {return "CityBoard [allCityMap=" + allCityMap + "]";}}/* ----------------Dna.java---------------- */ package cn.hanquan.ai.pojo;import java.util.Arrays;import org.apache.log4j.Logger;import cn.hanquan.ai.Init; import cn.hanquan.ai.util.Utils;/*** 染色體,對應一種旅行順序,命名為Order應該更準確* * @author luyang.gong**/ public class Dna {private static Logger logger = Logger.getLogger(Dna.class);/*** 采用整數編碼 e.g. 5 9 2 4 6 1 10 7 3 8*/public Integer arr[];/*** 適應值,越大越利于后代生存*/public Double adaptValue;/*** 產生后代的概率*/public Double prob;@Overridepublic String toString() {return "Dna [arr=" + Arrays.toString(arr) + "]";}/*** 生成具有num個數的染色體,并按順序賦初值* * @param num*/public Dna(int num) {Integer arr[] = new Integer[num];for (int i = 0; i < num; i++) {arr[i] = i + 1;}this.arr = arr;}}/* ----------------DnaPool.java---------------- */ package cn.hanquan.ai.pojo;import java.util.ArrayList;import org.apache.log4j.Logger;import cn.hanquan.ai.util.Utils;public class DnaPool {private static Logger logger = Logger.getLogger(Dna.class);/*** Dna池*/public ArrayList<Dna> dnaList;/*** 初始化空的Dna池*/public DnaPool() {this.dnaList = new ArrayList<Dna>();}/*** 指定Dna個數的Dna池* @param size 初始Dna個數*/public DnaPool(int size) {this.dnaList = new ArrayList<Dna>();for(int i=0;i<size;i++) {Dna dna = Utils.genRandomDna();dnaList.add(dna);}}}/* ----------------Node.java---------------- */ package cn.hanquan.ai.pojo;import org.apache.log4j.Logger;public class Node {private static Logger logger = Logger.getLogger(Node.class);public int num;public double posX;public double posY;@Overridepublic String toString() {return "Node [num=" + num + ", posX=" + posX + ", posY=" + posY + "]";}}/* ----------------ArrayUtils.java---------------- */ package cn.hanquan.ai.util;import java.util.Arrays; import java.util.Random;public class ArrayUtils {private static Random rand = new Random();private static <T> void swap(T[] a, int i, int j) {T temp = a[i];a[i] = a[j];a[j] = temp;}public static <T> void shuffle(T[] arr) {int length = arr.length;for (int i = length; i > 0; i--) {int randInd = rand.nextInt(i);swap(arr, randInd, i - 1);}}public static void main(String[] args) {Integer[] arr = { 1, 2, 3, 4, 5, 6, 7 };shuffle(arr);System.out.println(Arrays.toString(arr));} }/* ----------------CrossUtil.java---------------- */ package cn.hanquan.ai.util;import java.util.ArrayList;import cn.hanquan.ai.Init; import cn.hanquan.ai.pojo.Dna;/*** 交配操作* * @author luyang.gong**/ public class CrossUtil {/*** 進行交配* * @param d1* @param d2* @return 含有兩個Dna的ArrayList*/public static ArrayList<Dna> doCross(Dna d1, Dna d2) {Dna d1_cpy = new Dna(Init.CITYNUM);Dna d2_cpy = new Dna(Init.CITYNUM);for (int i = 0; i < Init.CITYNUM; i++) {// 手動實現deepcopyd1_cpy.arr[i] = d1.arr[i];d2_cpy.arr[i] = d2.arr[i];}int crossCount = (int) (Init.CITYNUM * Init.PCRatio);// 交配位數for (int i = 0; i < crossCount; i++) {// 直接交換double rand = Math.random();int index = (int) (Init.CITYNUM * rand);int temp = d1_cpy.arr[index]; // 交換d1_cpy.arr[index] = d2_cpy.arr[index];d2_cpy.arr[index] = temp;}// 處理沖突while (true) {if(getConfIndex(d1_cpy.arr) == -1) {break;}int confIndex = getConfIndex(d1_cpy.arr);for (int i = 0; i < Init.CITYNUM; i++) {int curNum = d2_cpy.arr[i];if (notHasNum(d1_cpy.arr, curNum)) {d1_cpy.arr[confIndex] = curNum;break;}}}// 處理沖突while (true) {if(getConfIndex(d2_cpy.arr) == -1) {break;}int confIndex = getConfIndex(d2_cpy.arr);for (int i = 0; i < Init.CITYNUM; i++) {int curNum = d1_cpy.arr[i];if (notHasNum(d2_cpy.arr, curNum)) {d2_cpy.arr[confIndex] = curNum;break;}}}ArrayList<Dna> twoDna = new ArrayList<Dna>();twoDna.add(d1_cpy);twoDna.add(d2_cpy);return twoDna;}/*** 獲取數組重復元素的索引,如果沒有重復元素,返回-1* * @param arr* @return 重復元素的索引*/private static int getConfIndex(Integer[] arr) {Integer[] hasNum = new Integer[Init.CITYNUM + 1];for (int i = 0; i < Init.CITYNUM + 1; i++) {hasNum[i] = 0;}for (int i = 0; i < arr.length; i++) {if (0 == hasNum[arr[i]]) {// 當前沒重復hasNum[arr[i]]++;} else {// 當前有重復return i;}} // for (int i = 0; i < arr.length; i++) { // System.out.print(arr[i] + " "); // } // System.out.println("沒重復");return -1;// 整體沒重復}/*** 數組中是否含有某個數* * @param arr* @param num* @return*/private static boolean notHasNum(Integer[] arr, int num) {for (int i = 0; i < arr.length; i++) {if (arr[i] == num) {return false;}}return true;}/*** 交換數組中指定兩個位置數值* @param arr* @param n1* @param n2*/private static void swap(Integer[] arr, int n1, int n2) {int temp = arr[n1];arr[n1] = arr[n2];arr[n2] = temp;}public static void main(String[] args) {// 測試用Dna d1 = new Dna(Init.CITYNUM);swap(d1.arr, 2, 5);swap(d1.arr, 3, 6);swap(d1.arr, 8, 7);swap(d1.arr, 15, 11);swap(d1.arr, 3, 14);Dna d2 = new Dna(Init.CITYNUM);swap(d1.arr, 1, 2);swap(d1.arr, 5, 8);swap(d1.arr, 3, 11);swap(d1.arr, 4, 15);System.out.println(d1);System.out.println(d2);System.out.println(doCross(d1, d2));} }/* ----------------MutateUtil.java---------------- */ package cn.hanquan.ai.util;import java.util.ArrayList;import cn.hanquan.ai.Init; import cn.hanquan.ai.pojo.Dna;/*** 變異操作* @author luyang.gong**/ public class MutateUtil {/*** 選擇幾個位置進行交換* @param dna* @return*/public static Dna doMutate(Dna dna) {Dna dna_cpy = new Dna(Init.CITYNUM);for (int i = 0; i < Init.CITYNUM; i++) {// 手動實現deepcopydna_cpy.arr[i] = dna.arr[i];}int mutateCount = (int) (Init.CITYNUM * Init.PMRatio);// 變異位數for (int i = 0; i < mutateCount; i++) {int index1 = (int) (Init.CITYNUM * Math.random());int index2 = (int) (Init.CITYNUM * Math.random());swap(dna_cpy.arr, index1, index2);}return dna_cpy;}private static void swap(Integer[] arr, int n1, int n2) {int temp = arr[n1];arr[n1] = arr[n2];arr[n2] = temp;}public static void main(String[] args) {Dna d = new Dna(Init.CITYNUM);System.out.println(d);System.out.println(doMutate(d));} }/* ----------------Utils.java---------------- */ package cn.hanquan.ai.util;import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Random;import org.apache.log4j.Logger;import cn.hanquan.ai.Init; import cn.hanquan.ai.pojo.CityBoard; import cn.hanquan.ai.pojo.Dna; import cn.hanquan.ai.pojo.DnaPool; import cn.hanquan.ai.pojo.Node;/*** 用于計算的工具類* * @author luyang.gong**/ public class Utils {private static Logger logger = Logger.getLogger(Utils.class);/*** 讀取所有城市坐標* * @return cityBoard*/public static CityBoard readCityBoard() {CityBoard cityBoard = new CityBoard();BufferedReader br = new BufferedReader(new InputStreamReader(System.in));for (int i = 0; i < Init.CITYNUM; i++) {String str = "";try {str = br.readLine().trim();} catch (IOException e) {e.printStackTrace();}String[] arr = str.split(" ");int num = Integer.parseInt(arr[0]);Node node = new Node();node.num = num;node.posX = Double.parseDouble(arr[1]);node.posY = Double.parseDouble(arr[2]);System.out.println(node);cityBoard.allCityMap.put(num, node);}return cityBoard;}/*** 隨機生成Dna* * @return*/public static Dna genRandomDna() {Dna dna = new Dna(Init.CITYNUM);ArrayUtils.shuffle(dna.arr);return dna;}/*** 直接輸入答案* * @return*/public static Dna genSolutionDna() {Dna dna = new Dna(Init.CITYNUM);dna.arr = Init.ANSWERDNA;return dna;}/*** 計算旅行總距離* * @param dna* @return*/public static double calTotalDistance(Dna dna) {double totalDistance = 0;for (int i = 0; i < Init.CITYNUM - 1; i++) {Node n1 = Init.CITYBOARD.allCityMap.get(dna.arr[i]);Node n2 = Init.CITYBOARD.allCityMap.get(dna.arr[i + 1]);double dis = Utils.calDistance(n1, n2);totalDistance += dis;}Node firstNode = Init.CITYBOARD.allCityMap.get(dna.arr[0]);Node lastNode = Init.CITYBOARD.allCityMap.get(dna.arr[Init.CITYNUM-1]); // logger.info("firstNode="+firstNode); // logger.info("lastNode="+lastNode);double goBackDis = Utils.calDistance(firstNode, lastNode);totalDistance += goBackDis;logger.debug(totalDistance);return totalDistance;}/*** 根據旅行總距離,計算適應值。適應值越大,越利于繁衍。* @return*/public static double calAdaptValue(double totalDistance) { // double adaptVal = 1 / (Init.MAXADAPT - totalDistance); // if (adaptVal > Init.MAXADAPT) { // Init.MAXADAPT = adaptVal; // }double adaptVal = Init.MAXADAPT - totalDistance;return adaptVal;}/*** 計算并更新Dna池中所有Dna的適應值*/public static void updateAdaptValueForAll(DnaPool dnaPool) {ArrayList<Dna> dnaList = dnaPool.dnaList;for(int i=0;i<dnaList.size();i++) {Dna dna = dnaList.get(i);double totalDistance = calTotalDistance(dna);dna.adaptValue=calAdaptValue(totalDistance);}}private static double calProb(double curAdaptValue,double totalAdaptValue) {return curAdaptValue/totalAdaptValue;}/*** 計算并更新Dna池中所有Dna的繁衍概率* @param dnaPool*/public static void updateProbForAll(DnaPool dnaPool) {ArrayList<Dna> dnaList = dnaPool.dnaList;Double totalAdaptValue = 0.0;for (int i = 0; i < dnaList.size(); i++) {// 計算適應值之和Dna dna = dnaList.get(i);totalAdaptValue += dna.adaptValue;}for (int i = 0; i < dnaList.size(); i++) {// 計算個體適應值Dna dna = dnaList.get(i);dna.prob = calProb(dna.adaptValue, totalAdaptValue);}}/*** 計算兩城市之間的距離* * @param n1 城市1* @param n2 城市2* @return*/public static double calDistance(Node n1, Node n2) {double square = (n1.posX - n2.posX) * (n1.posX - n2.posX) + (n1.posY - n2.posY) * (n1.posY - n2.posY);double distance = Math.pow(square, 0.5);logger.debug("(" + n1.posX + "," + n1.posY + ")" + "(" + n2.posX + "," + n2.posY + ")" + "distance=" + distance);return distance;}/*** 輪盤賭算法* @param dnaPool* @return*/public static Dna getRandCycleDna(DnaPool dnaPool) {double rand = Math.random(); // logger.info("rand = "+rand);ArrayList<Dna> dnaList = dnaPool.dnaList;double curBegin = 0;double curEnd = 0;// for (int i = 0; i < dnaList.size(); i++) { // Dna curDna = dnaList.get(i); // logger.info("curDna.prob = "+curDna.prob); // }for (int i = 0; i < dnaList.size(); i++) {Dna curDna = dnaList.get(i);curEnd += curDna.prob;logger.debug("[" + curBegin + ", " + curEnd + "]");if (rand >= curBegin && rand <= curEnd) {return curDna;} else {curBegin += curDna.prob;}}logger.error("輪盤賭異常");return null;}}總結
以上是生活随笔為你收集整理的【人工智能导论】遗传算法求解TSP问题(含源码github)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Docker容器的十大误区
- 下一篇: 【Git】撤销已经git add的文件