- VRP问题概述
- 解决算法分类
- 项目描述
- 算法结果
车辆路线问题(VRP)最早是由Dantzig和Ramser于1959年首次提出,它是指一定数量的客户,各自有不同数量的货物需求,配送中心向客户提供货物,由一个车队负责分送货物,组织适当的行车路线,目标是使得客户的需求得到满足,并能在一定的约束下,达到诸如路程最短、成本最小、耗费时间最少等目的。
VRP问题有很多子问题:
-
the capacitated vehicle routing problem (CVRP) , 即classical VRP
-
the vehicle routing problem with time windows (VRPTW) , 带时间窗 - VRPHTW 硬时间窗 | VRPSTW 软时间窗 | VRPTD(VRP with Time Deadlines)带顾客最迟服务时间
-
the Multiple Depot Vehicle Routing Problem (MDVRP) , 多车场
-
the Period Vehicle Routing Problem (PVRP) , 周期车辆路径问题
一般使用精确算法 或 启发式算法
- 精确算法适用于小规模问题,能得到最优解。
- direct tree search , 直接树搜索 | dynamic programming , 动态规划 | integer linear programming , 整数线性规划
- 启发式算法用于大规模问题,能快速找出可行解。
- Simulated Annealing 模拟退火
- Tabu Search 禁忌搜索
- Genetic Algoritm 遗传算法 | Genetic Programming 遗传规划
- Genetic Network Programming 遗传网络规划
- ACS, Ant Colony System 蚁群算法
我主要是研究了蚁群算法和CW节约算法,发现后者思路比较清晰,并且我们项目的需求也不复杂,所以基于后者的思想来实现。
考虑这样的需求:
某集散中心管辖10个邮局,已知集散中心和各营业点的经纬度,寄达各支局和各支局收寄的邮件, 时间窗口。
邮车装载邮件数不同。邮车的运行成本为3元/公里, 速度为30km每小时。试用最少邮车,并规划邮车的行驶路线使总费用最省。
那么输入参数需要包括:
- 各个节点的经纬度,邮件收寄数,邮件送达数,时间窗(如果有要求的话,包括最早、最晚到达时间),装卸货时间
- 可用车辆的载重
输出结果就是算法形成的路径,每条路径中包括到达邮局的先后次序。
问题的解决步骤是: 读入数据、构建路径 、合并路径、优化。
优化阶段对 可行解 进行了节点的调整,缩短行车路线。
目前已经基于CW节约算法,实现 载重量 约束 以及 时间窗口约束,使用Java作为实现。
邮局类
1 package vrp; 2 3 import java.util.Objects; 4 5 /** 6 * @author 陈海越 7 * @version 1.0 8 * @since 新标准版5.0 9 */ 10 public class PostOffice implements Cloneable { 11 12 public PostOffice(int index, String name, float x, float y, 13 float receive, float sendOut, 14 int earliestTime, int latestTime, int duration, 15 int type) { 16 this.index = index; 17 this.name = name; 18 this.x = x; 19 this.y = y; 20 this.receive = receive; 21 this.sendOut = sendOut; 22 this.earliestTime = earliestTime; 23 this.latestTime = latestTime; 24 this.duration = duration; 25 this.type = type; 26 } 27 28 /** 29 * 序号 30 */ 31 private int index; 32 33 private String name; 34 35 private float x; 36 37 private float y; 38 39 private float receive; 40 41 private float sendOut; 42 43 /** 44 * 最早到达时间 45 */ 46 private int earliestTime; 47 48 /** 49 * 最晚到达时间 50 */ 51 private int latestTime; 52 53 /** 54 * 到达时间 55 */ 56 private int arrivedTime; 57 58 private int duration; 59 60 private int type; 61 62 private Route currentRoute; 63 64 private PostOffice previousNode; 65 66 private PostOffice nextNode; 67 68 public String getName() { 69 return name; 70 } 71 72 public void setName(String name) { 73 this.name = name; 74 } 75 76 public float getSendOut() { 77 return sendOut; 78 } 79 80 public void setSendOut(float sendOut) { 81 this.sendOut = sendOut; 82 } 83 84 public PostOffice getPreviousNode() { 85 return previousNode; 86 } 87 88 public void setPreviousNode(PostOffice previousNode) { 89 this.previousNode = previousNode; 90 } 91 92 public PostOffice getNextNode() { 93 return nextNode; 94 } 95 96 public void setNextNode(PostOffice nextNode) { 97 this.nextNode = nextNode; 98 } 99 100 public int getArrivedTime() { 101 return arrivedTime; 102 } 103 104 public void setArrivedTime(int arrivedTime) { 105 this.arrivedTime = arrivedTime; 106 } 107 108 109 public Route getCurrentRoute() { 110 return currentRoute; 111 } 112 113 public void setCurrentRoute(Route currentRoute) { 114 this.currentRoute = currentRoute; 115 } 116 117 public int getIndex() { 118 return index; 119 } 120 121 public float getX() { 122 return x; 123 } 124 125 public float getY() { 126 return y; 127 } 128 129 public float getReceive() { 130 return receive; 131 } 132 133 public int getEarliestTime() { 134 return earliestTime; 135 } 136 137 public int getLatestTime() { 138 return latestTime; 139 } 140 141 public int getDuration() { 142 return duration; 143 } 144 145 public int getType() { 146 return type; 147 } 148 149 public float distanceTo(PostOffice p2) { 150 return distanceTo(y, x, p2.y, p2.x, 'K'); 151 } 152 153 /** 154 * 使用经纬度计算,返回距离 155 * @param lat1 纬度1 156 * @param lon1 经度1 157 * @param lat2 纬度2 158 * @param lon2 经度2 159 * @param unit 'K' 公里 ,默认 英里 160 * @return 161 */ 162 private float distanceTo(double lat1, double lon1, double lat2, double lon2, char unit) { 163 double theta = lon1 - lon2; 164 double dist = Math.sin(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(deg2rad(theta)); 165 dist = Math.acos(dist); 166 dist = rad2deg(dist); 167 dist = dist * 60 * 1.1515; 168 if (unit == 'K') { 169 dist = dist * 1.609344; 170 } 171 return (float)(dist); 172 } 173 174 private double deg2rad(double deg) { 175 return (deg * Math.PI / 180.0); 176 } 177 178 private double rad2deg(double rad) { 179 return (rad * 180.0 / Math.PI); 180 } 181 182 public int getDepartTime() { 183 return arrivedTime + duration; 184 } 185 186 @Override 187 public boolean equals(Object o) { 188 if (this == o) return true; 189 if (o == null || getClass() != o.getClass()) return false; 190 PostOffice that = (PostOffice) o; 191 return Float.compare(that.x, x) == 0 && 192 Float.compare(that.y, y) == 0; 193 } 194 195 @Override 196 public String toString() { 197 return "PostOffice{" + index + 198 " (" + x + 199 ", " + y + 200 ")}"; 201 } 202 203 @Override 204 public Object clone() throws CloneNotSupportedException { 205 PostOffice clone = (PostOffice) super.clone(); 206 clone.setCurrentRoute(currentRoute == null ? null : 207 (Route) currentRoute.clone()); 208 return clone; 209 } 210 211 public String getTimeInterval() { 212 return index + " [到达时间:" + convertHHmm(arrivedTime) + 213 ", 出发时间:" + convertHHmm(getDepartTime()) + 214 "]"; 215 } 216 217 public String convertHHmm(int mins) { 218 return (mins < 60 ? "0:" : mins/60 + ":") + mins%60 + ""; 219 } 220 221 public String getCoordinate() { 222 return index + " [" + y + ", " + x + "]"; 223 } 224 225 @Override 226 public int hashCode() { 227 return Objects.hash(x, y); 228 } 229 }
路径类
1 package vrp; 2 3 import java.util.Collections; 4 import java.util.Comparator; 5 import java.util.LinkedList; 6 import java.util.List; 7 import java.util.stream.Collectors; 8 9 /** 10 * @author 陈海越 11 * @version 1.0 12 * @since 新标准版5.0 13 * 14 *18 */ 19 public class Route implements Cloneable{ 20 21 public static final double DEFAULT_DELTA = 0.0001; 22 private LinkedList15 * 历史: 16 * 建立: 2019/9/3 陈海越 17 *
节约距离类
1 package vrp; 2 3 /** 4 * @author 陈海越 5 * @version 1.0 6 * @since 新标准版5.0 7 * 8 *12 */ 13 public class SavedDistance implements Comparable { 14 15 private PostOffice p1; 16 private PostOffice p2; 17 private float savedDistance; 18 19 public SavedDistance(PostOffice p1, PostOffice p2, float savedDistance) { 20 this.p1 = p1; 21 this.p2 = p2; 22 this.savedDistance = savedDistance; 23 } 24 25 public PostOffice getP1() { 26 return p1; 27 } 28 29 public PostOffice getP2() { 30 return p2; 31 } 32 33 public PostOffice getAnother(PostOffice p) { 34 if (p.equals(p1)) { 35 return p2; 36 } else if (p.equals(p2)) { 37 return p1; 38 } 39 return null; 40 } 41 42 public float getSavedDistance() { 43 return savedDistance; 44 } 45 46 @Override 47 public String toString() { 48 return "SD{" + 49 "(" + p1 + 50 " -> " + p2 + 51 "), saved=" + savedDistance + 52 '}'; 53 } 54 55 @Override 56 public int compareTo(Object o) { 57 return Float.compare(savedDistance, ((SavedDistance) o).savedDistance); 58 } 59 60 public PostOffice nodeAt(Route existRoute) throws Exception { 61 if (existRoute.getNodes().contains(p1)) { 62 return p1; 63 } else if (existRoute.getNodes().contains(p2)) { 64 return p2; 65 } 66 67 throw new Exception("p1:" + p1 + ", p2:" + p2 +". 均不存在于路径:" + existRoute); 68 } 69 }9 * 历史: 10 * 建立: 2019/9/3 陈海越 11 *
程序入口
1 package vrp; 2 3 import java.io.BufferedReader; 4 import java.io.File; 5 import java.io.FileNotFoundException; 6 import java.io.FileReader; 7 import java.io.IOException; 8 import java.util.ArrayList; 9 import java.util.Arrays; 10 import java.util.Collections; 11 import java.util.Comparator; 12 import java.util.LinkedList; 13 import java.util.List; 14 15 /** 16 * @author 陈海越 17 * @version 1.0 18 * @since 新标准版5.0 19 * 20 *24 */ 25 public class VRPTest { 26 27 public static final String KONGGE = "\\s+|\r"; 28 public static final int FACTOR = 1; 29 private int vehicleNumber; 30 private int totalPointNumber; 31 private LinkedList21 * 历史: 22 * 建立: 2019/9/2 陈海越 23 *
测试数据
12 32 20 20 20 8 7.5 19 18.5 3 2 2 15 15 0 邮件处理中心 113.401158 22.937741 0 0 360 1200 0 1 市桥营业部 113.400252 22.938145 1.5 2.0 360 1200 30 2 南村营业部 113.401893 23.018498 1.5 2.0 360 1200 30 3 南沙营业部 113.506397 22.816508 1.5 2.0 360 1200 30 4 大石营业部 113.314550 23.003639 1.5 2.0 360 1200 30 5 洛溪营业部 113.326329 23.039990 1.5 2.0 360 1000 30 6 石基营业部 113.442812 22.958920 2.0 1.5 360 1200 30 7 桥南营业部 113.341478 22.928405 2.0 1.5 360 1200 30 9 金山营业部 113.357242 22.987939 2.0 1.5 360 1200 30 10 德兴投递部 113.385036 22.941521 2.0 1.5 360 1200 30 11 禺山投递部 113.371736 22.940598 2.0 1.5 360 1200 30 12 富都投递部 113.374778 22.962895 2.0 1.5 360 1200 30 13 桥南投递部 113.376950 22.928157 2.0 1.5 360 1200 30 14 石基投递部 113.442540 22.958869 2.0 1.5 360 1200 30 15 大石投递部 113.312418 23.029387 2.0 1.5 360 1200 30 16 丽江投递部 113.308222 23.041347 2.0 1.5 360 1200 30 17 钟村投递部 113.323570 22.983256 2.0 1.5 360 1200 30 18 沙湾投递部 113.346612 22.907224 2.0 1.5 360 1200 30 19 祈福投递部 113.343419 22.973618 2.0 1.5 360 1200 30 20 南村投递部 113.391632 23.002452 2.0 1.5 360 1200 30 21 石楼投递部 113.323820 22.983377 2.0 1.5 360 1200 30 22 新造投递部 113.424587 23.041629 2.0 1.5 360 1200 30 23 化龙投递部 113.472498 23.033740 2.0 1.5 360 1200 30 24 东涌投递部 113.461433 22.891050 2.0 1.5 360 1200 30 25 鱼窝头投递部 113.465328 22.856062 2.0 1.5 360 1200 30 26 南沙投递部 113.538039 22.792315 2.0 1.5 360 1200 30 27 黄阁投递部 113.516492 22.829905 2.0 1.5 360 1200 30 28 大岗投递部 113.412975 22.806085 2.0 1.5 360 1200 30 29 榄核投递部 113.346429 22.844289 2.0 1.5 360 1200 30 30 万顷沙投递部 113.558386 22.712772 2.0 1.5 360 1200 30 31 新垦投递部 113.613264 22.650771 2.0 1.5 360 1200 30 32 横沥投递部 113.494007 22.737961 2.0 1.5 360 1200 30