用混合遗传算法求解物流配送路径

问题:

从某物流中心用多台配送车辆向多个客户送货,每个客户的位置和货物需求量一定,每台配送车辆的载重量一定,其一次配送的最大行驶距离一定,要求合理安排车辆配送路线,使目标函数得到优化,并满足以下条件:

(1) 每条配送路径上各客户的需求量之和不超过配送车辆的载重量;

(2) 每条配送路径的长度不超过配送车辆一次配送的最大行驶距离;

(3) 每个客户的需求必须满足,且只能由一台配送车辆送货。

若以配送总里程最短为目标函数,则可建立如下优化问题的数学模型

《用混合遗传算法求解物流配送路径》《用混合遗传算法求解物流配送路径》《用混合遗传算法求解物流配送路径》《用混合遗传算法求解物流配送路径》《用混合遗传算法求解物流配送路径》《用混合遗传算法求解物流配送路径》

思路:

(1) 编码方法的确定

直接生产L 个1~L 间的互不重复的自然数的排列,该排列即构成一个个体。按照物流配送路径优化问题的约束条件,可依次将个体中的元素(客户) 划入各条配送路径中。
设某个体中的客户排列为41235 ,可用如下方法得到其对应的配送路径方案:首先将客户4 作为第一个客户加入到配送路径1 中,然后判断能否满足问题的约束条件,即客户4 的需求量是否超过第一台车辆的最大载重量,且路径0 – 4 – 0 的长度是否超过第一台车辆一次配送的最大行驶距离,设能够满足,则可将客户1 作为第三个客户加入到路径1 中,然后判断能否满足问题的约束条件,设仍能满足,则可将客户2 作为第三个客户加入到路径1 中,设仍能满足问题的约束条件,则可将客户3作为第四个客户加入到路径1 中,设此时不能满足
问题的约束条件,这说明客户3 不能加入到路径1中,由此可行第一条配送路径:0 – 4 – 1 – 2 – 0 ;然后,将客户3 作为第一个客户加入配送路径2 中。
若某个个体对应的配送路径数大于配送车辆总台数,则说明该个体对应一个不可行解

(2) 初始群体的确定

随机产生一种1~L 这L个互不重复的自然数的排列,即形成一个个体。设群体规模为N ,则通过随机产生N 个这样的个体,即可形成初始群体。

(3) 适应度评估方法的确定

对于某个个体所对应的配送路径方案,要判定其优劣,一是要看其是否满足问题的约束条件;二是要计算其目标函数值。
对于某个个体,设其对应的配送路径方案的配送路径条数与配送车辆总台数之差为M(若配送路径条数≤配送车辆总台数,则取M = 0 ,表示该个体对应一个可行解;若配送路径条数> 车辆总台数,则M > 0 ,表示该个体对应一个不可行解) ,其目标函数值为Z ,将M 看成该个体对应的配送路径方案的不可行路径条数,并设对每条不可行路径的惩罚权重为Pw (该权重可根据目标函数的取值范围取一个相对较大的正数) ,则该个体的适应度F 可用公式计算。
F = 1/ (Z + M ×Pw)

(4) 选择操作

采用如下最佳个体保留与赌轮选择相结合的选择策略:将每代群体中的N 个个体按适应度由大到小排列,排在第一位的个体性能最优,将它复制一个直接进入下一代,并排在第一位;下一代群体的另N – 1 个个体需要根据前代群体的N 个个体的适应度,采用赌轮选择法产生,具体地说,就是首先计算上代群体中所有个体适应度的总和ΣFj ,再计算每个个体的适应度所占的比例Fj/ΣFj (j = 1 ,2 , ⋯,N) ,以此作为其被选择的概率。
既可保证最优个体生存至下一代,又能保证适应度较大的个体以较大的机会进入下一代

(5) 交叉操作

除排在第一位的最优个体外,另N – 1 个个体要按交叉概率Pc 进行配对交叉重组。
采用类OX法实施交叉操作,现举例说明其操作方法: 
①随机在父代个体中选择一个交配区域,如两父代个体及交配区域选定为:A = 47| 8563| 921 ,B = 83| 4691|257 ;
②将B 的交配区域加到A 的前面,A 的交配区域加到B 的前面,得:A’= 4691| 478563921 ,B’=8563| 834691257 ;
③在A’、B’中自交配区域后依次删除与交配区相同的自然数,得到最终的两个体为:A”= 469178532 ,B”= 856349127 。

(6) 变异操作

采用连续多次对换的变异技术,使个体在排列顺序上有较大的变化。变异操作是以概率Pm 发生的,一旦变异操作发生,则用随机方法产生交换次数J ,对所需变异操作的个体的基因进行J 次对换(对换基因的位置也是随机产生的) 。

(7) 爬山操作

对于通过遗传操作形成的每代群体中的最优个体,要通过邻域搜索实施爬山操作。采用基因换位算子实现爬山操作,其具体操作方法是:
①在个体中随机选择两个基因,并交换它们的位置: 
②判断基因换位后其适应值是否增加,若适应值增加,则以换位后的个体取代原个体; 
③重复①、②,直到达到一定的交换次数为止。

(8) 终止准则

采用进化指定代数的终止准则。

例:

某物流中心有2 台配送车辆,其载重量均为8t ,车辆每次配送的最大行驶距离为50km ,配送中心(其编号为0) 与8 个客户之间及8 个客户相互之间的距离dij 、8 个客户的货物需求量qj (i 、j = 1 ,2 , ⋯,8) 均见表1 。要求合理安排车辆配送路线,使配送总里程最短。
采用以下参数:群体规模取20 ,进化代数取25 ,交叉概率取019 ,变异概率取0109 ,变异时基因换位次数取5 , 对不可行路径的惩罚权重取100km ,实施爬山操作时爬山次数取20 。对实例随机求解10 次。

代码:

public class GeneticAlgorithm {
	double[][] d = { { 0, 4, 6, 7.5, 9, 20, 10, 16, 8 },
			{ 4, 0, 6.5, 4, 10, 5, 7.5, 11, 10 },
			{ 6, 6.5, 0, 7.5, 10, 10, 7.5, 7.5, 7.5 },
			{ 7.5, 4, 7.5, 0, 10, 5, 9, 9, 15 },
			{ 9, 10, 10, 10, 0, 10, 7.5, 7.5, 10 },
			{ 20, 5, 10, 5, 10, 0, 7, 9, 7.5 },
			{ 10, 7.5, 7.5, 9, 7.5, 7, 0, 7, 10 },
			{ 16, 11, 7.5, 9, 7.5, 9, 7, 0, 10 },
			{ 8, 10, 7.5, 15, 10, 7.5, 10, 10, 0 } };
	double[] q = { 0, 1, 2, 1, 2, 1, 4, 2, 2 };

	Random random = new Random();

	int rows;
	int time;
	int mans;
	int cars;
	int tons;
	int dis;
	int PW;

	double JCL = 0.9;
	double BYL = 0.09;
	int JYHW = 5;
	int PSCS = 20;

	/**
	 *
	 * @param rows
	 *            排列个数
	 * @param time
	 *            迭代次数
	 * @param mans
	 *            客户数量
	 * @param cars
	 *            货车数量
	 * @param tons
	 *            货车载重
	 * @param distance
	 *            货车最大行驶距离
	 * @param PW
	 *            惩罚因子
	 *
	 */
	public GeneticAlgorithm(int rows, int time, int mans, int cars, int tons,
			int distance, int PW) {
		this.rows = rows;
		this.time = time;
		this.mans = mans;
		this.cars = cars;
		this.tons = tons;
		this.dis = distance;
		this.PW = PW;
	}

	public String run() {
		int[][] lines = new int[rows][mans];
		// 适应度
		double[] fit = new double[rows];

		// 获取rows个随机排列,并计算适应度
		int j = 0;
		for (int i = 0; i < rows; i++) {
			j = 0;
			while (j < mans) {
				int num = random.nextInt(mans) + 1;
				if (!isHas(lines[i], num)) {
					lines[i][j] = num;
					j++;

					// System.out.print(num + ",");
				}
			}

			// System.out.println();

			fit[i] = calFitness(lines[i]);

			// System.out.println(fit[i]);
		}

		int t = 0;

		while (t < time) {
			int[][] nextLines = new int[rows][mans];
			// 适应度
			double[] nextFit = new double[rows];

			double[] ranFit = new double[rows];
			double totalFit = 0, tempFit = 0;

			for (int i = 0; i < rows; i++) {
				totalFit += fit[i];
			}
			for (int i = 0; i < rows; i++) {
				ranFit[i] = tempFit + fit[i] / totalFit;
				tempFit += ranFit[i];
			}

			// 上代最优直接到下一代
			double m = fit[0];
			int ml = 0;

			for (int i = 0; i < rows; i++) {
				if (m < fit[i]) {
					m = fit[i];
					ml = i;
				}
			}

			for (int i = 0; i < mans; i++) {
				nextLines[0][i] = lines[ml][i];
			}
			nextFit[0] = fit[ml];

			// 最优使用爬山算法
			clMountain(nextLines[0]);

			int nl = 1;

			while (nl < rows) {
				// 根据概率选取排列
				int r = ranSelect(ranFit);

				// 判断是否交叉 不能超出界限
				if (random.nextDouble() < JCL && nl + 1 < rows) {
					int[] fLine = new int[mans];
					int[] nLine = new int[mans];

					// 获取交叉排列
					int rn = ranSelect(ranFit);

					// 获得交叉的段
					int f = random.nextInt(mans);
					int l = random.nextInt(mans);
					int min, max, fpo = 0, npo = 0;

					if (f < l) {
						min = f;
						max = l;
					} else {
						min = l;
						max = f;
					}

					// 将截取的段加入新生成的基因
					while (min <= max) {
						fLine[fpo] = lines[rn][min];
						nLine[npo] = lines[r][min];

						min++;
						fpo++;
						npo++;
					}

					for (int i = 0; i < mans; i++) {
						if (!isHas(fLine, lines[r][i])) {
							fLine[fpo] = lines[r][i];
							fpo++;
						}

						if (!isHas(nLine, lines[rn][i])) {
							nLine[npo] = lines[rn][i];
							npo++;
						}
					}

					// 基因变异
					change(fLine);
					change(nLine);

					// 加入下一代
					for (int i = 0; i < mans; i++) {
						nextLines[nl][i] = fLine[i];
						nextLines[nl + 1][i] = nLine[i];
					}

					nextFit[nl] = calFitness(fLine);
					nextFit[nl + 1] = calFitness(nLine);

					nl += 2;
				} else {
					int[] line = new int[mans];

					int i = 0;
					while (i < mans) {
						line[i] = lines[r][i];

						i++;
					}

					// 基因变异
					change(line);

					// 加入下一代
					i = 0;
					while (i < mans) {
						nextLines[nl][i] = line[i];

						i++;
					}

					nextFit[nl] = calFitness(line);

					nl++;
				}
			}

			// 新的一代覆盖上一代
			for (int i = 0; i < rows; i++) {
				for (int h = 0; h < mans; h++) {
					lines[i][h] = nextLines[i][h];
				}
				fit[i] = nextFit[i];
			}

			t++;
		}

		// 上代最优
		double m = fit[0];
		int ml = 0;

		for (int i = 0; i < rows; i++) {
			if (m < fit[i]) {
				m = fit[i];
				ml = i;
			}
		}

//		System.out.println("最优结果为:");
//		for (int i = 0; i < mans; i++) {
//			System.out.print(lines[ml][i] + ",");
//		}
//		System.out.println();

		return showResult(lines[ml]);
	}

	/**
	 * 爬山算法
	 *
	 * @param line
	 */
	public void clMountain(int[] line) {
		double oldFit = calFitness(line);
		int i = 0;
		while (i < PSCS) {
			int f = random.nextInt(mans);
			int n = random.nextInt(mans);

			change(line, f, n);

			double newFit = calFitness(line);

			if (newFit < oldFit) {
				change(line, f, n);
			}

			i++;
		}
	}

	/**
	 * 基因变异
	 *
	 * @param line
	 */
	public void change(int[] line) {
		if (random.nextDouble() < BYL) {
			int i = 0;
			while (i < JYHW) {
				int f = random.nextInt(mans);
				int n = random.nextInt(mans);

				change(line, f, n);

				i++;
			}
		}
	}

	public void change(int[] line, int f, int n) {
		int temp = line[f];

		line[f] = line[n];
		line[n] = temp;
	}

	/**
	 * 选择随机的序列
	 *
	 * @param ranFit
	 * @return
	 */
	public int ranSelect(double[] ranFit) {
		double ran = random.nextDouble();

		for (int i = 0; i < rows; i++) {
			if (ran < ranFit[i])
				return i;
		}
		System.out.println("ERROR!!! get ranSelect Error!");
		return 0;
	}

	/**
	 * 计算适应度
	 *
	 * @param line
	 * @return
	 */
	public double calFitness(int[] line) {
		double carTon = 0;
		double carDis = 0;
		double newTon = 0;
		double newDis = 0;
		double totalDis = 0;
		// 默认为2倍
		int[][] ll = new int[cars * 2][mans];
		int r = 0, l = 0, fore = 0, M = 0;
		for (int i = 0; i < mans; i++) {
			// 距离
			newDis = carDis + d[fore][line[i]];
			// 载重
			newTon = carTon + q[line[i]];
			// 超过货车距离,载重
			if ((newDis + d[line[i]][0]) > dis || newTon > tons) {
				// 下一辆车
				totalDis += carDis + d[fore][0];
				l = 0;
				r++;
				fore = 0;
				i--;
				carTon = 0;
				carDis = 0;
			} else {
				carDis = newDis;
				carTon = newTon;
				fore = line[i];

				ll[r][l] = line[i];
				l++;
			}
		}
		// 加上最后一辆货车距离
		totalDis += carDis + d[fore][0];

		// for (int i = 0; i < cars * 2; i++) {
		// for (int j = 0; j < mans; j++) {
		// System.out.print(ll[i][j]);
		// }
		// System.out.println();
		// }
		// System.out.println("总路径长度为:" + totalDis);

		if ((r - cars + 1) > 0)
			M = r - cars + 1;

		// 目标函数
		double result = 1 / (totalDis + M * PW);

		return result;
	}

	public String showResult(int[] line) {
		double carTon = 0;
		double carDis = 0;
		double newTon = 0;
		double newDis = 0;
		double totalDis = 0;
		// 默认为2倍
		int[][] ll = new int[cars * 2][mans];
		int r = 0, l = 0, fore = 0, M = 0;
		for (int i = 0; i < mans; i++) {
			// 距离
			newDis = carDis + d[fore][line[i]];
			// 载重
			newTon = carTon + q[line[i]];
			// 超过货车距离,载重
			if ((newDis + d[line[i]][0]) > dis || newTon > tons) {
				// 下一辆车
				totalDis += carDis + d[fore][0];
				l = 0;
				r++;
				fore = 0;
				i--;
				carTon = 0;
				carDis = 0;
			} else {
				carDis = newDis;
				carTon = newTon;
				fore = line[i];

				ll[r][l] = line[i];
				l++;
			}
		}
		// 加上最后一辆货车距离
		totalDis += carDis + d[fore][0];

        StringBuffer sb = new StringBuffer();

		for (int i = 0; i < cars; i++) {
			for (int j = 0; j < mans; j++) {
//				System.out.print(ll[i][j]);
                sb.append(ll[i][j]);
			}
//			System.out.println();
            sb.append("\n");
		}
//		System.out.println("总路径长度为:" + totalDis);
        sb.append("总路径长度为:" + totalDis + "\n");

		if ((r - cars + 1) > 0)
			M = r - cars + 1;

		// 目标函数
		double result = 1 / (totalDis + M * PW);

//		System.out.println("最终权值为:" + result);
        sb.append("最终权值为:" + result + "\n");

        return sb.toString();
	}

	public boolean isHas(int[] line, int num) {
		for (int i = 0; i < mans; i++) {
			if (line[i] == num)
				return true;
		}

		return false;
	}

	public static void main(String[] args) {
		GeneticAlgorithm ga = new GeneticAlgorithm(20, 25, 8, 2, 8, 50, 100);

		for (int i = 0; i < 10; i++) {
			System.out.println("第" + (i + 1) + "次:");
			long begin = System.currentTimeMillis();
			ga.run();
			long end = System.currentTimeMillis();
			System.out.println("使用时间" + (end - begin) + "毫秒");
			System.out.println();
		}
	}
}

输出结果:

第1次:
最优结果为:
4,7,5,3,1,8,6,2,
47531000
86200000
00000000
00000000
总路径长度为:70.0
最终权值为:0.014285714285714285
使用时间31毫秒

第2次:
最优结果为:
4,7,6,2,8,5,3,1,
47600000
28531000
00000000
00000000
总路径长度为:67.5
最终权值为:0.014814814814814815
使用时间9毫秒

第3次:
最优结果为:
8,2,3,5,1,4,7,6,
82351000
47600000
00000000
00000000
总路径长度为:70.5
最终权值为:0.014184397163120567
使用时间8毫秒

第4次:
最优结果为:
2,7,4,8,1,3,5,6,
27480000
13560000
00000000
00000000
总路径长度为:69.0
最终权值为:0.014492753623188406
使用时间2毫秒

第5次:
最优结果为:
4,7,5,3,1,8,6,2,
47531000
86200000
00000000
00000000
总路径长度为:70.0
最终权值为:0.014285714285714285
使用时间2毫秒

第6次:
最优结果为:
1,3,5,8,2,6,7,4,
13582000
67400000
00000000
00000000
总路径长度为:67.5
最终权值为:0.014814814814814815
使用时间15毫秒

第7次:
最优结果为:
1,3,5,6,8,4,7,2,
13560000
84720000
00000000
00000000
总路径长度为:69.0
最终权值为:0.014492753623188406
使用时间2毫秒

第8次:
最优结果为:
2,7,4,8,6,5,3,1,
27480000
65310000
00000000
00000000
总路径长度为:69.0
最终权值为:0.014492753623188406
使用时间3毫秒

第9次:
最优结果为:
6,7,4,1,3,5,8,2,
67400000
13582000
00000000
00000000
总路径长度为:67.5
最终权值为:0.014814814814814815
使用时间64毫秒

第10次:
最优结果为:
8,6,4,1,3,5,7,2,
86400000
13572000
00000000
00000000
总路径长度为:70.0
最终权值为:0.014285714285714285
使用时间3毫秒

    原文作者:遗传算法
    原文地址: https://blog.csdn.net/z363115269/article/details/22945665
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞