Java遗传算法求解给定初始点的TSP问题

一、TSP问题

TSP问题(Travelling Salesman Problem)即旅行商问题,又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。

TSP问题是一个组合优化问题。该问题可以被证明具有NPC计算复杂性。TSP问题可以分为两类,一类是对称TSP问题(Symmetric TSP),另一类是非对称问题(Asymmetric TSP)。所有的TSP问题都可以用一个图(Graph)来描述:

什么是对称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 5468 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列子我也做过,用动态规划做的,想看的人可以去看一下,数据如下:

二、遗传算法

遗传算法(Genetic Algorithms )是基于生物进化理论的原理发展起来的一种广为应用的、高效的随机搜索与优化的方法。其主要特点是群体搜索策略和群体中个体之间的信息交换,搜索不依赖于梯度信息。它是在70年代初期由美国密西根( Michigan )大学的霍兰( Holland )教授发展起来的。1975年霍兰教授发表了第一本比较系统论述遗传算法的专著《自然系统与人工系统中的适应性》(《 Adaptationin Natural and Artificial Systems 》)。遗传算法最初被研究的出发点不是为专门解决最优化问题而设计的,它与进化策略、进化规划共同构成了进化算法的主要框架,都是为当时人工智能的发展服务的。迄今为止,遗传算法是进化算法中最广为人知的算法。

 遗传算法的实施步骤如下(以目标函数求最小为例)。
    第一步:初始化 t←0进化代数计数器;T是最大进化代数;随机生成M个个体作为初始群体P(t);
    第二步:个体评价 计算P(t)中各个个体的适应度;
    第三步:选择运算 将选择算子作用于群体;
    第四步:交叉运算 将交叉算子作用于群体;
    第五步:变异运算 将变异算子作用于群体,并通过以上运算得到下一代群体P(t + 1);
    第六步:终止条件判断  t≦T:t← t+1 转到步骤2;t>T:终止 输出解。

遗传算法应用步骤:
    1)确定决策变量及各种约束条件,即个体的表现型X和问题的解空间;
    2)建立优化模型 (目标函数最大OR 最小) 数学描述形式 量化方法;
    3)染色体编码方法;
    4)解码方法;
    5)个体适应度的量化评价方法 F(x)
    6)设计遗传算子;
    7)确定有关运行参数。

三、遗传算法求解TSP问题

遗传算法的第一步肯定是确定染色体的编码方式,具体的编码方式有很多,不再一一介绍了,这个问题很简单,就是用整数编码就可以,对于每个城市用一个整数来编号,例如有48个城市,就用0到47来标识每一个城市,然后一个路径就是一条染色体编码,染色体长度为48,如:0,1,2,3,4...47就是一个染色体,它表达的意思就是旅行者从0号城市出发,依次访问1,2,...47号城市再回到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
今日推荐