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