天天看點

用混合遺傳算法求解物流配送路徑

問題:

從某物流中心用多台配送車輛向多個客戶送貨,每個客戶的位置和貨物需求量一定,每台配送車輛的載重量一定,其一次配送的最大行駛距離一定,要求合理安排車輛配送路線,使目标函數得到優化,并滿足以下條件:

(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毫秒

繼續閱讀