Java 遺伝的アルゴリズムは初期点を与えられた TSP 問題を解決します

1.TSPの問題

TSP問題(巡回セールスマン問題)とは、巡回セールスマン問題、巡回セールスマン問題、セールスマン問題とも訳され、数学の分野では有名な問題の一つです。n 都市を訪れたい旅行中のビジネスマンがいるとします。彼は自分が進みたい道を選択しなければなりません。その道の制限は、各都市は 1 回しか訪問できず、最終的には元の出発都市に戻らなければならないということです。パス選択の目的は、パスの距離がすべてのパスの中で最小値であることを要求することです。

TSP 問題は組み合わせ最適化問題です。この問題には NPC の計算が複雑であることが証明されています。TSP 問題は 2 つのカテゴリに分類できます。1 つは対称 TSP 問題 (Symmetric TSP)、もう 1 つは非対称問題 (Asymetric TSP) です。すべての TSP 問題はグラフで説明できます。

対称 TSP 問題とは何ですか? この記事で使用したデータは次のとおりです。

1 6734 1453 
2 2233 10 
3 5530 1424 
4 401 841 5 3082 1644 
6 7608 4458 
7 7573 3716  8 7265 1268 9 
6898  1885  10 1112 2049 11 5 468 2606 12 5989 2873 13  4706  2674  14 4612 2035  15 6347 2683  16 6107 669  17  7611 5184  18 7462 3590  19 7732 4723  20 5900 3561  21 4483 3369 22 6101 1110  23 5199 2182  24 1633 2809  25 4307 2322  26  675 1006  27 7555 4819 28 7541 3981  29 3177 756  30 7352 4506  31  7545 2801  32 3245 3305 

























33 6426 3173 
34 4608 1198 
35 23 2216 
36 7248 3779 
37 7762 4595 38 7392 2244  39 3484 2829 
40 6271  2135 41 4985  140 42 1916 1569 43  7280 4899  44  7509 3239 45 10 2676  46  6807 2993  47 5185 3258  48 3023 1942









これを data.txt として保存し、ドライブ E のルート ディレクトリに配置して実行します。データは点 (x, y) であり、平面図内の座標です。次の式を使用して距離を計算します。

なぜユークリッド距離を計算するのに 10 で割る必要があるのか​​については、編集者にはわかりませんが、このデータの距離の式が次のようになっているということだけを理解すればよく、問題に陥る必要はありません (もし誰かがいるとしても)これは単なるデータ準備のステップであり、完全に彼と話す必要はありません、重要なのは考え方です。なぜ対称と呼ばれるのですか? それは、点 A から点 B までの距離が、点 B から点 A までの距離とまったく等しいためです。これを対称と呼びます。では、非対称とは何でしょうか? 理解するのは簡単です。実際の距離を指すのではなく、価格になることがあります。たとえば、大連から北京までの列車の料金は 200 元で、北京から大連までの列車は同じです (同じ列車)価格は300元です。編集者は例を示しているだけです。例として、動的計画法を使用した非対称TSPの例も作成しました。見たい人は見てください。データは次のとおりです:

 

2. 遺伝的アルゴリズム

遺伝的アルゴリズムは、生物進化理論の原理に基づいて開発された、広く使用されている効率的なランダム検索および最適化手法です。その主な特徴は、グループ探索戦略とグループ内の個人間の情報交換であり、探索は勾配情報に依存しません。1970 年代初頭にミシガン大学のホランド教授によって開発されました。1975 年、ホランド教授は、遺伝的アルゴリズムを体系的に論じた最初のモノグラフ「Adaptation in Natural and Artificial Systems」を出版しました。遺伝的アルゴリズムの初期研究の出発点は、最適化問題を解決するために特別に設計されたものではありませんでしたが、進化戦略と進化計画とともに、進化アルゴリズムの主要なフレームワークを構成し、そのすべてが当時の人工知能の発展に役立ちました。遺伝的アルゴリズムは、進化的アルゴリズムの中で最も広く知られています。

 遺伝的アルゴリズムの実装手順は次のとおりです (例として目的関数の最小化を取り上げます)。
    第 1 ステップ: t←0 進化世代カウンターを初期化します; T は最大進化世代です; 初期グループ P(t) として M 個の個体をランダムに生成します; 第 2 ステップ: 個体評価は P(t) における各個体の適応度を計算し
    ます
    ; 3 番目のステップ: 選択操作は母集団に選択
    演算子を適用します; 4 番目のステップ: 交叉操作は母集団
    に交叉演算子を適用します; 5 番目のステップ: 突然変異演算は母集団に突然変異演算子を適用します。演算により、次世代母集団 P を求める (t + 1);
    ステップ 6: 終了条件判定 t≦T: t← t+1 ステップ 2 へ; t>T: 出力解を終了する。

遺伝的アルゴリズムの適用手順: 1)
    決定変数とさまざまな制約、つまり個人の
    表現型の
    決定方法;
    4) 解読方法;
    5) 個人の適応度 F(x) の定量的評価方法;
    6) 遺伝的演算子の設計;
    7) 関連する決定動作パラメータ。

 

3. TSP問題を解決するための遺伝的アルゴリズム

遺伝的アルゴリズムの最初のステップは、間違いなく、染色体のエンコード方法を決定することです。具体的なエンコード方法はたくさんありますが、一つずつ紹介しません。この問題は非常に簡単です。整数エンコードを使用し、整数を使用するだけです。各都市に番号を付けます。たとえば、48 の都市がある場合、0 ~ 47 を使用して各都市を識別します。パスは染色体コードであり、染色体の長さは 48 です。例: 0,1,2,3 ,4...47 は染色体です。この表現の意味は、旅行者は都市 0 から出発し、順番に都市 1、2、...47 を訪れ、その後都市 0 に戻ります。第 2 のステップは、都市 0 を決定することです。遺伝的アルゴリズムの評価関数であり、適応度とも呼ばれます。遺伝子が優れていればいるほど、環境に適応しやすい集団の遺伝子をより多く残すことができるということを理解するのは簡単です。この質問は、次のような遺伝子を指します。次に、人口の増加に合わせて、特定のクロスオーバー オペレーターと遺伝的オペレーターをすべて自分で定義してプログラムすることができます。詳細なコードは次のとおりです。

package ga;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Random;

public class ga_init_point {

	private int point;//起始点
	private int scale;// 种群规模
	private int cityNum; // 城市数量,染色体长度
	private int MAX_GEN; // 运行代数
	private int[][] distance; // 距离矩阵
	private int bestT;// 最佳出现代数
	private int bestLength; // 最佳长度
	private int[] bestTour; // 最佳路径

	// 初始种群,父代种群,行数表示种群规模,一行代表一个个体,即染色体,列表示染色体基因片段
	private int[][] oldPopulation;
	private int[][] newPopulation;// 新的种群,子代种群
	private int[] fitness;// 种群适应度,表示种群中各个个体的适应度

	private float[] Pi;// 种群中各个个体的累计概率
	private float Pc;// 交叉概率
	private float Pm;// 变异概率
	private int t;// 当前代数

	private Random random;

	public ga_init_point() {

	}

	/**
	 * constructor of GA
	 * 
	 * @param p
	 * 	      初始点
	 * @param s
	 *            种群规模
	 * @param n
	 *            城市数量
	 * @param g
	 *            运行代数
	 * @param c
	 *            交叉率
	 * @param m
	 *            变异率
	 * 
	 **/
	public ga_init_point(int p, int s, int n, int g, float c, float m) {
		point = p;
		scale = s;
		cityNum = n;
		MAX_GEN = g;
		Pc = c;
		Pm = m;
	}
	
	private void init(String filename) throws IOException {
		// 读取数据
		int[] x;
		int[] y;
		String strbuff;
		BufferedReader data = new BufferedReader(new InputStreamReader(
				new FileInputStream(filename)));
		distance = new int[cityNum][cityNum];
		x = new int[cityNum];
		y = new int[cityNum];
		for (int i = 0; i < cityNum; i++) {
			// 读取一行数据,数据格式1 6734 1453
			strbuff = data.readLine();
			// 字符分割
			String[] strcol = strbuff.split(" ");
			x[i] = Integer.valueOf(strcol[1]);// x坐标
			y[i] = Integer.valueOf(strcol[2]);// y坐标
		}
		// 计算距离矩阵
		// 针对具体问题,距离计算方法也不一样,此处用的是att48作为案例,它有48个城市,距离计算方法为伪欧氏距离,最优值为10628(不确定起始点的情况下)
		for (int i = 0; i < cityNum - 1; i++) {
			distance[i][i] = 0; // 对角线为0
			for (int j = i + 1; j < cityNum; j++) {
				double rij = Math
						.sqrt(((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j])
								* (y[i] - y[j])) / 10.0);
				// 四舍五入,取整
				int tij = (int) Math.round(rij);
				if (tij < rij) {
					distance[i][j] = tij + 1;
					distance[j][i] = distance[i][j];
				} else {
					distance[i][j] = tij;
					distance[j][i] = distance[i][j];
				}
			}
		}
		distance[cityNum - 1][cityNum - 1] = 0;

		bestLength = Integer.MAX_VALUE;
		bestTour = new int[cityNum + 1];
		bestT = 0;
		t = 0;

		newPopulation = new int[scale][cityNum-1];
		oldPopulation = new int[scale][cityNum-1];
		fitness = new int[scale];
		Pi = new float[scale];

		random = new Random(System.currentTimeMillis());
	}

	// 初始化种群
	void initGroup() {
		int i, j, k;
		// Random random = new Random(System.currentTimeMillis());
		for (k = 0; k < scale; k++)// 种群数
		{
			oldPopulation[k][0] = random.nextInt(65535) % cityNum;
			while(oldPopulation[k][0]==point) {
				oldPopulation[k][0] = random.nextInt(65535) % cityNum;
			}
			for (i = 1; i < cityNum-1;)// 染色体长度
			{
				oldPopulation[k][i] = random.nextInt(65535) % cityNum;
				while(oldPopulation[k][i]==point) {
					oldPopulation[k][i] = random.nextInt(65535) % cityNum;
				}
				for (j = 0; j < i; j++) {
					if (oldPopulation[k][i] == oldPopulation[k][j]) {
						break;
					}
				}
				if (j == i) {
					i++;
				}
			}
		}	 
	}
	
	public int evaluate(int[] chromosome) {
		// 0123
		int len = 0;
		// 染色体,起始城市,城市1,城市2...城市n,计算长度(代价)
		for (int i = 1; i < cityNum-1; i++) {
			len += distance[chromosome[i - 1]][chromosome[i]];
		}
		// 起始城市到第一个城市的距离
		len += distance[point][chromosome[0]];
		// 城市n,起始城市(最后一个城市到起始城市的距离)
		len += distance[chromosome[cityNum - 2]][point];
		return len;
	}

	// 计算种群中各个个体的累积概率,前提是已经计算出各个个体的适应度fitness[max],作为赌轮选择策略一部分,Pi[max]
	void countRate() {
		int k;
		double sumFitness = 0;// 适应度总和

		double[] tempf = new double[scale];

		for (k = 0; k < scale; k++) {
			tempf[k] = 10.0 / fitness[k];
			sumFitness += tempf[k];
		}

		Pi[0] = (float) (tempf[0] / sumFitness);//0-pi[0]表示第一个个体被选到的累计概率区域
		for (k = 1; k < scale; k++) {
			Pi[k] = (float) (tempf[k] / sumFitness + Pi[k - 1]);
		}

		/*
		 * for(k=0;k<scale;k++) { System.out.println(fitness[k]+" "+Pi[k]); }
		 */
	}

	// 挑选某代种群中适应度最高的个体,直接复制到子代中
	// 前提是已经计算出各个个体的适应度Fitness[max]
	public void selectBestGh() {
		int k, i, maxid;
		int maxevaluation;

		maxid = 0;
		maxevaluation = fitness[0];
		for (k = 1; k < scale; k++) {
			if (maxevaluation > fitness[k]) {
				maxevaluation = fitness[k];
				maxid = k;
			}
		}

		if (bestLength > maxevaluation) {
			bestLength = maxevaluation;
			bestT = t;// 最好的染色体出现的代数;
			for (i = 0; i < cityNum-1; i++) {
				bestTour[i] = oldPopulation[maxid][i];
			}
		}

		// System.out.println("代数 " + t + " " + maxevaluation);
		// 复制染色体,k表示新染色体在种群中的位置,kk表示旧的染色体在种群中的位置
		copyGh(0, maxid);// 将当代种群中适应度最高的染色体k复制到新种群中,排在第一位0
	}

	// 复制染色体,k表示新染色体在种群中的位置,kk表示旧的染色体在种群中的位置
	public void copyGh(int k, int kk) {
		int i;
		for (i = 0; i < cityNum-1; i++) {
			newPopulation[k][i] = oldPopulation[kk][i];
		}
	}

	// 赌轮选择策略挑选
	public void select() {
		int k, i, selectId;
		float ran1;
		// Random random = new Random(System.currentTimeMillis());
		for (k = 1; k < scale; k++) {
			ran1 = (float) (random.nextInt(65535) % 1000 / 1000.0);
			// System.out.println("概率"+ran1);
			// 产生方式
			for (i = 0; i < scale; i++) {
				if (ran1 <= Pi[i]) {
					break;
				}
			}
			selectId = i;
			// System.out.println("选中" + selectId);
			copyGh(k, selectId);
		}
	}

	//进化函数,正常交叉变异
	public void evolution() {
		int k;
		// 挑选某代种群中适应度最高的个体
		selectBestGh();

		// 赌轮选择策略挑选scale-1个下一代个体
		select();

		// Random random = new Random(System.currentTimeMillis());
		float r;

		// 交叉方法
		for (k = 0; k < scale; k = k + 2) {
			r = random.nextFloat();//产生概率0-1
			// System.out.println("交叉率..." + r);
			if (r < Pc) {
				// System.out.println(k + "与" + k + 1 + "进行交叉...");
				//OXCross(k, k + 1);// 进行交叉
				OXCross1(k, k + 1);
			} else {
				r = random.nextFloat();// /产生概率
				// System.out.println("变异率1..." + r);
				// 变异
				if (r < Pm) {
					// System.out.println(k + "变异...");
					OnCVariation(k);
				}
				r = random.nextFloat();// /产生概率
				// System.out.println("变异率2..." + r);
				// 变异
				if (r < Pm) {
					// System.out.println(k + 1 + "变异...");
					OnCVariation(k + 1);
				}
			}

		}
	}

	//进化函数,保留最好染色体不进行交叉变异
	public void evolution1() {
		int k;
		// 挑选某代种群中适应度最高的个体
		selectBestGh();

		// 赌轮选择策略挑选scale-1个下一代个体
		select();

		// Random random = new Random(System.currentTimeMillis());
		float r;

		for (k = 1; k + 1 < scale / 2; k = k + 2) {
			r = random.nextFloat();// /产生概率
			if (r < Pc) {
				OXCross1(k, k + 1);// 进行交叉
				//OXCross(k,k+1);//进行交叉
			} else {
				r = random.nextFloat();// /产生概率
				// 变异
				if (r < Pm) {
					OnCVariation(k);
				}
				r = random.nextFloat();// /产生概率
				// 变异
				if (r < Pm) {
					OnCVariation(k + 1);
				}
			}
		}
		if (k == scale / 2 - 1)// 剩最后一个染色体没有交叉L-1
		{
			r = random.nextFloat();// /产生概率
			if (r < Pm) {
				OnCVariation(k);
			}
		}

	}

	// 类OX交叉算子
	void OXCross(int k1, int k2) {
		int i, j, k, flag;
		int ran1, ran2, temp;
		int[] Gh1 = new int[cityNum-1];
		int[] Gh2 = new int[cityNum-1];
		// Random random = new Random(System.currentTimeMillis());

		ran1 = random.nextInt(65535) % (cityNum-1);
		ran2 = random.nextInt(65535) % (cityNum-1);
		// System.out.println();
		// System.out.println("-----------------------");
		// System.out.println("----"+ran1+"----"+ran2);

		while (ran1 == ran2) {
			ran2 = random.nextInt(65535) % (cityNum-1);
		}

		if (ran1 > ran2)// 确保ran1<ran2
		{
			temp = ran1;
			ran1 = ran2;
			ran2 = temp;
		}
		// System.out.println();
		// System.out.println("-----------------------");
		// System.out.println("----"+ran1+"----"+ran2);
		// System.out.println("-----------------------");
		// System.out.println();
		flag = ran2 - ran1 + 1;// 删除重复基因前染色体长度
		for (i = 0, j = ran1; i < flag; i++, j++) {
			Gh1[i] = newPopulation[k2][j];
			Gh2[i] = newPopulation[k1][j];
		}
		// 已近赋值i=ran2-ran1个基因

		for (k = 0, j = flag; j < cityNum-1;)// 染色体长度
		{
			Gh1[j] = newPopulation[k1][k++];
			for (i = 0; i < flag; i++) {
				if (Gh1[i] == Gh1[j]) {
					break;
				}
			}
			if (i == flag) {
				j++;
			}
		}

		for (k = 0, j = flag; j < cityNum-1;)// 染色体长度
		{
			Gh2[j] = newPopulation[k2][k++];
			for (i = 0; i < flag; i++) {
				if (Gh2[i] == Gh2[j]) {
					break;
				}
			}
			if (i == flag) {
				j++;
			}
		}

		for (i = 0; i < cityNum-1; i++) {
			newPopulation[k1][i] = Gh1[i];// 交叉完毕放回种群
			newPopulation[k2][i] = Gh2[i];// 交叉完毕放回种群
		}

		// System.out.println("进行交叉--------------------------");
		// System.out.println(k1+"交叉后...");
		// for (i = 0; i < cityNum; i++) {
		// System.out.print(newPopulation[k1][i] + "-");
		// }
		// System.out.println();
		// System.out.println(k2+"交叉后...");
		// for (i = 0; i < cityNum; i++) {
		// System.out.print(newPopulation[k2][i] + "-");
		// }
		// System.out.println();
		// System.out.println("交叉完毕--------------------------");
	}

	// 交叉算子,相同染色体交叉产生不同子代染色体
	public void OXCross1(int k1, int k2) {
		int i, j, k, flag;
		int ran1, ran2, temp;
		int[] Gh1 = new int[cityNum-1];
		int[] Gh2 = new int[cityNum-1];
		// Random random = new Random(System.currentTimeMillis());

		ran1 = random.nextInt(65535) % (cityNum-1);
		ran2 = random.nextInt(65535) % (cityNum-1);
		while (ran1 == ran2) {
			ran2 = random.nextInt(65535) % (cityNum-1);
		}

		if (ran1 > ran2)// 确保ran1<ran2
		{
			temp = ran1;
			ran1 = ran2;
			ran2 = temp;
		}

		// 将染色体1中的第三部分移到染色体2的首部0-ran1,ran1-ran2,ran2-48
		for (i = 0, j = ran2; j < cityNum-1; i++, j++) {
			Gh2[i] = newPopulation[k1][j];
		}

		flag = i;// 染色体2原基因开始位置

		for (k = 0, j = flag; j < cityNum-1;)// 染色体长度,用k2的顺序去补全Gh2
		{
			Gh2[j] = newPopulation[k2][k++];
			for (i = 0; i < flag; i++) {
				if (Gh2[i] == Gh2[j]) {
					break;
				}
			}
			if (i == flag) {
				j++;
			}
		}

		flag = ran1;
		for (k = 0, j = 0; k < cityNum-1;)// 染色体长度
		{
			Gh1[j] = newPopulation[k1][k++];
			for (i = 0; i < flag; i++) {
				if (newPopulation[k2][i] == Gh1[j]) {
					break;
				}
			}
			if (i == flag) {
				j++;
			}
		}

		flag = cityNum-1 - ran1;

		for (i = 0, j = flag; j < cityNum-1; j++, i++) {
			Gh1[j] = newPopulation[k2][i];
		}

		for (i = 0; i < cityNum-1; i++) {
			newPopulation[k1][i] = Gh1[i];// 交叉完毕放回种群
			newPopulation[k2][i] = Gh2[i];// 交叉完毕放回种群
		}
	}

	// 多次对换变异算子
	public void OnCVariation(int k) {
		int ran1, ran2, temp;
		int count;// 对换次数

		// Random random = new Random(System.currentTimeMillis());
		count = random.nextInt(65535) % (cityNum-1);

		for (int i = 0; i < count; i++) {

			ran1 = random.nextInt(65535) % (cityNum-1);
			ran2 = random.nextInt(65535) % (cityNum-1);
			while (ran1 == ran2) {
				ran2 = random.nextInt(65535) % (cityNum-1);
			}
			temp = newPopulation[k][ran1];
			newPopulation[k][ran1] = newPopulation[k][ran2];
			newPopulation[k][ran2] = temp;
		}

		/*
		 * for(i=0;i<L;i++) { printf("%d ",newGroup[k][i]); } printf("\n");
		 */
	}

	public void solve() {
		int i;
		int k;

		// 初始化种群
		initGroup();
		// 计算初始化种群适应度,Fitness[max]
		for (k = 0; k < scale; k++) {
			fitness[k] = evaluate(oldPopulation[k]);
			// System.out.println(fitness[k]);
		}
		// 计算初始化种群中各个个体的累积概率,Pi[max]
		countRate();
		System.out.println("初始种群...");
		for (k = 0; k < scale; k++) {
			for (i = 0; i < cityNum-1; i++) {
				System.out.print(oldPopulation[k][i] + ",");
			}
			System.out.println();
			System.out.println("----" + fitness[k] + " " + Pi[k]);
		}
		
		for (t = 0; t < MAX_GEN; t++) {
			//evolution();
			evolution1();
			// 将新种群newGroup复制到旧种群oldGroup中,准备下一代进化
			for (k = 0; k < scale; k++) {
				for (i = 0; i < cityNum-1; i++) {
					oldPopulation[k][i] = newPopulation[k][i];
				}
			}
			// 计算种群适应度
			for (k = 0; k < scale; k++) {
				fitness[k] = evaluate(oldPopulation[k]);
			}
			// 计算种群中各个个体的累积概率
			countRate();
		}

		System.out.println("最后种群...");
		for (k = 0; k < scale; k++) {
			for (i = 0; i < cityNum-1; i++) {
				System.out.print(oldPopulation[k][i] + ",");
			}
			System.out.println();
			System.out.println("---" + fitness[k] + " " + Pi[k]);
		}

		System.out.println("最佳长度出现代数:");
		System.out.println(bestT);
		System.out.println("最佳长度");
		System.out.println(bestLength);
		System.out.println("最佳路径:");
		System.out.print(point+"-->");
		for (i = 0; i < cityNum-1; i++) {
			System.out.print(bestTour[i] + "-->");
		}
		System.out.print(point);
	}

	
	/**
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		System.out.println("Start....");
		ga_init_point ga = new ga_init_point(0,30, 48, 10000, 0.7f, 0.9f);
		ga.init("E://data.txt");
		ga.solve();
	}

}

結果:

最適な長さの出現代数:
9765
最適な長さ
13303
最適なパス:

0-->7-->8-->39-->10-->14-->37-->30-->43-->6-->17-->27-->35- ->42-->29-->5-->26-->18-->16-->36-->45-->11-->32-->19-->46--> 20-->12-->13-->22-->31-->38-->4-->28-->1-->9-->41-->25-->3- ->44-->34-->23-->47-->24-->33-->40-->2-->21-->15-->0

パラメーターの反復回数、交叉確率、突然変異確率を変更すると、より良い結果が得られる可能性がありますが、ここでは一例を示しているだけです。

 

おすすめ

転載: blog.csdn.net/Jeff_fei/article/details/81021782