转载

基于C-W节约算法的车辆路径规划问题的Java实现

 

 

车辆路线问题(VRP)最早是由Dantzig和Ramser于1959年首次提出,它是指一定数量的客户,各自有不同数量的货物需求,配送中心向客户提供货物,由一个车队负责分送货物,组织适当的行车路线,目标是使得客户的需求得到满足,并能在一定的约束下,达到诸如路程最短、成本最小、耗费时间最少等目的。

 

VRP问题有很多子问题:

  • the capacitated vehicle routing problem (CVRP) , classical VRP

  • the vehicle routing problem with time windows (VRPTW) , 带时间窗 - VRPHTW 硬时间窗      VRPSTW 软时间窗      VRPTDVRP 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每小时。试用最少邮车,并规划邮车的行驶路线使总费用最省。

 

 

那么输入参数需要包括:

  1. 各个节点的经纬度,邮件收寄数,邮件送达数,时间窗(如果有要求的话,包括最早、最晚到达时间),装卸货时间
  2. 可用车辆的载重

 

输出结果就是算法形成的路径,每条路径中包括到达邮局的先后次序。

 

问题的解决步骤是: 读入数据、构建路径 、合并路径、优化。

优化阶段对 可行解 进行了节点的调整,缩短行车路线。

 

 

 

目前已经基于CW节约算法,实现 载重量 约束 以及 时间窗口约束,使用Java作为实现。

 

邮局类

  1 package vrp;
  2 
  3 import java.util.Objects;
  4 
  5 /**
  6  * @author <a herf="chenhy@itek-china.com">陈海越</a>
  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 }
PostOffice.java

 

路径类

  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 <a herf="chenhy@itek-china.com">陈海越</a>
 11  * @version 1.0
 12  * @since 新标准版5.0
 13  *
 14  * <pre>
 15  * 历史:
 16  *      建立: 2019/9/3 陈海越
 17  *        </pre>
 18  */
 19 public class Route implements Cloneable{
 20 
 21     public static final double DEFAULT_DELTA = 0.0001;
 22     private LinkedList<PostOffice> nodes;
 23 
 24     private Float capacity = 0f;
 25 
 26     private Float totalReceive = 0f;
 27 
 28     private Float totalSendOut = 0f;
 29 
 30     private Float length = 0f;
 31 
 32     /**
 33      *  公里每分钟
 34      */
 35     private Float speed = 0.5f;
 36 
 37     public void setNodesAndUpdateLoad(List<PostOffice> nodes) {
 38         this.nodes = new LinkedList<>(nodes);
 39         for (int i = 0; i < nodes.size(); i++) {
 40             PostOffice node = nodes.get(i);
 41             addReceive(node.getReceive());
 42             addSendOut(node.getSendOut());
 43         }
 44     }
 45 
 46     public void setCapacity(Float capacity) {
 47         this.capacity = capacity;
 48     }
 49 
 50     public Float getSpeed() {
 51         return speed;
 52     }
 53 
 54     public void setSpeed(Float speed) {
 55         this.speed = speed;
 56     }
 57 
 58     public void setTotalReceive(Float totalReceive) {
 59         this.totalReceive = totalReceive;
 60     }
 61 
 62     public Float getTotalReceive() {
 63         return totalReceive;
 64     }
 65 
 66     public Float getTotalSendOut() {
 67         return totalSendOut;
 68     }
 69 
 70     public void setTotalSendOut(Float totalSendOut) {
 71         this.totalSendOut = totalSendOut;
 72     }
 73 
 74     public Float getCapacity() {
 75         return capacity;
 76     }
 77 
 78     public LinkedList<PostOffice> getNodes() {
 79         return nodes;
 80     }
 81 
 82     public Float getLength() {
 83         return length;
 84     }
 85 
 86     public void setLength(Float length) {
 87         this.length = length;
 88     }
 89 
 90     public void addReceive(Float receive) {
 91         totalReceive += receive;
 92     }
 93 
 94     public void addSendOut(Float sendOut) {
 95         totalSendOut += sendOut;
 96     }
 97 
 98     public Float calcLength(LinkedList<PostOffice> nodes) {
 99         Float length = 0f;
100         if (!nodes.isEmpty()) {
101             PostOffice firstNode = nodes.getFirst();
102             for (int i=1;i<nodes.size();i++) {
103                 PostOffice next = nodes.get(i);
104                 length += next.distanceTo(firstNode);
105                 firstNode = next;
106             }
107         }
108         return length;
109     }
110 
111     public boolean twoOptOptimise(){
112         //交换中间路径 任意两点,尝试优化路径
113         boolean optimised = false;
114         for (int i = 1; i < nodes.size()-1; i++) {
115             for (int j = i+1; j < nodes.size()-1; j++) {
116                 LinkedList<PostOffice> tempList = (LinkedList<PostOffice>) nodes.clone();
117                 int k = i, l = j;
118                 while (k < l) {
119                     Collections.swap(tempList, k, l);
120                     k++;l--;
121                 }
122                 Float tempLength = calcLength(tempList);
123                 if (length - tempLength > DEFAULT_DELTA) {
124                     //优化成功
125                     nodes = tempList;
126                     length = tempLength;
127                     updateNodeTracing();
128                     updateArrivedTime();
129                     optimised = true;
130                 }
131             }
132         }
133         return optimised;
134     }
135 
136     /**
137      * 更新路径上点的前后关系
138      */
139     public void updateNodeTracing() {
140         PostOffice previous = nodes.get(0);
141         for (int i = 1; i < nodes.size(); i++) {
142             PostOffice node = nodes.get(i);
143             //设置点的前后关系
144             node.setPreviousNode(previous);
145             previous.setNextNode(node);
146             previous = node;
147         }
148     }
149 
150     public void updateArrivedTime() {
151         PostOffice previous = nodes.get(0);
152         previous.setArrivedTime(previous.getEarliestTime());
153         for (int i = 1; i < nodes.size(); i++) {
154             PostOffice node = nodes.get(i);
155             // 节点到达时间为 离开上一节点时间加上路程时间
156             int arrivedTime = previous.getDepartTime() + (int)((node.distanceTo(previous)) / speed);
157             node.setArrivedTime(arrivedTime);
158             previous = node;
159         }
160 
161     }
162 
163     @Override
164     public String toString() {
165         return (nodes == null ? "[]" : nodes.stream().map(PostOffice::getCoordinate).collect(Collectors.toList()).toString()) +
166                 ", 车辆载重=" + capacity +
167                 ", 总送达=" + totalReceive +
168                 ", 总收寄=" + totalSendOut +
169                 ", 总长度=" + length + "公里" +
170                 ", 速度=" + speed * 60 + "公里每小时";
171     }
172 
173     public String timeSchedule() {
174         return "到达时间{" +
175                 "邮局=" + (nodes == null ? "[]" : nodes.stream().map(PostOffice::getTimeInterval).collect(Collectors.toList()).toString());
176     }
177 
178     @Override
179     public Object clone() throws CloneNotSupportedException {
180         return super.clone();
181     }
182 
183     /**
184      * 硬时间窗限制
185      * 到达时间 不能早于最早时间,不能晚于最晚时间
186      * @param p1
187      * @return
188      */
189     public boolean hardTimeWindowFeasible(PostOffice p1) {
190         PostOffice previous = p1;
191         int lastDepart = previous.getDepartTime();
192         for (PostOffice node : nodes) {
193             int arrivedTime = lastDepart + (int)((node.distanceTo(previous)) / speed);
194             if (arrivedTime < node.getEarliestTime() || arrivedTime > node.getLatestTime() ) {
195                 return false;
196             }
197             lastDepart = arrivedTime + node.getDuration();
198             previous = node;
199         }
200         return true;
201     }
202 
203 
204     public boolean vehicleOptimise(LinkedList<Float> vehicleCapacityList, LinkedList<Float> usedVehicleList) {
205 
206         vehicleCapacityList.sort(Comparator.naturalOrder());
207         for (Float temp : vehicleCapacityList) {
208             if (temp < this.capacity) {
209                 if (temp > this.totalReceive) {
210                     Float curLoad = totalReceive;
211                     boolean cando = true;
212                     for (PostOffice node : nodes) {
213                         if ( curLoad - node.getReceive() + node.getSendOut() > temp) {
214                             cando = false;
215                             break;
216                         }
217                         curLoad = curLoad - node.getReceive() + node.getSendOut();
218                     }
219                     if (cando) {
220                         vehicleCapacityList.remove(temp);
221                         vehicleCapacityList.add(capacity);
222                         usedVehicleList.remove(capacity);
223                         usedVehicleList.add(temp);
224                         this.capacity = temp;
225                         return true;
226                     }
227                 }
228 
229             }
230         }
231         return false;
232     }
233 }
Route.java

 

节约距离类

 1 package vrp;
 2 
 3 /**
 4  * @author <a herf="chenhy@itek-china.com">陈海越</a>
 5  * @version 1.0
 6  * @since 新标准版5.0
 7  *
 8  * <pre>
 9  * 历史:
10  *      建立: 2019/9/3 陈海越
11  *        </pre>
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 }
SaveDistance.java

 

程序入口

  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 <a herf="chenhy@itek-china.com">陈海越</a>
 17  * @version 1.0
 18  * @since 新标准版5.0
 19  *
 20  * <pre>
 21  * 历史:
 22  *      建立: 2019/9/2 陈海越
 23  *        </pre>
 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 LinkedList<Float> vehicleCapacityList = new LinkedList<>();
 32     private LinkedList<Float> usedVehicleList = new LinkedList<>();
 33     private List<PostOffice> postOfficeList = new ArrayList<>();
 34     private List<Route> routeList = new ArrayList<>();
 35     private float[][] distMatrix;
 36     private List<SavedDistance> savingList = new ArrayList<>();
 37 
 38 
 39     public static void main(String[] args) throws Exception {
 40         VRPTest vrpTest = new VRPTest();
 41         vrpTest.readFromFile("C:\\Users\\Administrator\\Documents\\vrp_data\\test3.txt");
 42         vrpTest.vrp();
 43     }
 44 
 45     /**
 46      * 从文件中读取数据
 47      */
 48     public void readFromFile(String fileName) {
 49         File file = new File(fileName);
 50         try {
 51             BufferedReader br = new BufferedReader(new FileReader(
 52                     file));
 53             constructGeneral(br);
 54             constructVehicle(br);
 55             constructNodes(br);
 56         } catch (FileNotFoundException e) {
 57             e.printStackTrace();
 58         } catch (IOException e) {
 59             e.printStackTrace();
 60         }
 61     }
 62 
 63     private void constructGeneral(BufferedReader br) throws IOException {
 64         String first = br.readLine().trim();
 65         String[] firstLineArr = first.split(KONGGE);
 66         vehicleNumber = Integer.parseInt(firstLineArr[0]);
 67         totalPointNumber = Integer.parseInt(firstLineArr[1]);
 68     }
 69 
 70     private void constructVehicle(BufferedReader br) throws IOException {
 71         String vehicleCapacity = br.readLine().trim();
 72         for (String s : vehicleCapacity.split(KONGGE)) {
 73             vehicleCapacityList.add(Float.parseFloat(s));
 74         }
 75     }
 76 
 77     private void constructNodes(BufferedReader br) throws IOException {
 78         for (int i = 0; i < totalPointNumber; i++) {
 79             String postStr = br.readLine().trim();
 80             String[] postArr = postStr.split(KONGGE);
 81             PostOffice postOffice =
 82                     new PostOffice(Integer.parseInt(postArr[0]), postArr[1],
 83                             Float.parseFloat(postArr[2]),
 84                             Float.parseFloat(postArr[3]),
 85                             Float.parseFloat(postArr[4]),
 86                             Float.parseFloat(postArr[5]),
 87                             Integer.parseInt(postArr[6]),
 88                             Integer.parseInt(postArr[7]),
 89                             Integer.parseInt(postArr[8]),
 90                             isDepot(i));
 91             postOfficeList.add(postOffice);
 92         }
 93     }
 94 
 95     private int isDepot(int i) {
 96         //第一条记录为仓库
 97         return i == 0 ? 0 : 1;
 98     }
 99 
100     public void vrp() throws Exception {
101         calcDistMatrix();
102         calcSavingMatrix();
103         calcRoute();
104         cwSaving();
105         //optimise
106         twoOptOptimise();
107         capacityOptimise();
108 
109         printGeneral();
110         printRoute();
111 //        printTimeSchedule();
112     }
113 
114     /**
115      * 计算距离矩阵
116      */
117     private void calcDistMatrix() {
118         int length = postOfficeList.size();
119         distMatrix = new float[length][length];
120         for (int i = 0; i < totalPointNumber; i++) {
121             for (int j = 0; j < i; j++) {
122                 distMatrix[i][j] = postOfficeList.get(i).distanceTo(postOfficeList.get(j));
123                 distMatrix[j][i] = distMatrix[i][j];
124             }
125             distMatrix[i][i] = 0;
126         }
127     }
128 
129     /**
130      * 计算节约距离列表
131      */
132     private void calcSavingMatrix() {
133         for (int i = 2; i < totalPointNumber; i++) {
134             for (int j = 1; j < i; j++) {
135                 PostOffice pi = postOfficeList.get(i);
136                 PostOffice pj = postOfficeList.get(j);
137                 PostOffice depot = postOfficeList.get(0);
138                 float dist = pi.distanceTo(pj);
139                 float saving =
140                         pi.distanceTo(depot) + pj.distanceTo(depot) - dist;
141                 savingList.add(new SavedDistance(postOfficeList.get(i), postOfficeList.get(j), saving));
142             }
143         }
144         savingList.sort(Collections.reverseOrder());
145     }
146 
147     private boolean twoOptOptimise() {
148         for (Route route : routeList) {
149             if (route.twoOptOptimise()) {
150                 return true;
151             }
152         }
153         return false;
154     }
155 
156     private boolean capacityOptimise() {
157         for (Route route : routeList) {
158             if (route.vehicleOptimise(vehicleCapacityList, usedVehicleList)) {
159                 return true;
160             }
161         }
162         return false;
163     }
164 
165 
166 
167     /**
168      * 构建基础路径
169      */
170     private void calcRoute() throws CloneNotSupportedException {
171         //将所有点单独与集散中心组成一条路径,路径对象中包含集散中心
172         PostOffice depot = postOfficeList.get(0);
173         for(int i = 1 ; i<postOfficeList.size(); i++) {
174             Route r = new Route();
175             //更新点 所在路径
176             PostOffice startNode = (PostOffice) depot.clone();
177             startNode.setCurrentRoute(r);
178             PostOffice endNode = (PostOffice) depot.clone();
179             endNode.setCurrentRoute(r);
180             postOfficeList.get(i).setCurrentRoute(r);
181             //更新路径 上的点
182             r.setNodesAndUpdateLoad(new LinkedList<>(Arrays.asList(startNode, postOfficeList.get(i), endNode)));
183 
184             //更新到达时间
185             r.updateArrivedTime();
186             //更新路径长度
187             r.setLength(r.calcLength(r.getNodes()));
188             //更新原路径上点的前后关系
189             r.updateNodeTracing();
190             //更新载重
191             routeList.add(r);
192         }
193     }
194 
195     /**
196      * CW节约算法构建路程
197      * @throws Exception
198      */
199     private void cwSaving() throws Exception {
200         //取出save值最大的路径,尝试加入当前路径
201         for (SavedDistance savedDistance : savingList) {
202             mergeSavedDistance(savedDistance);
203         }
204     }
205 
206     /**
207      * 合并路径规则:
208      * 两点中有一点在路径尾部,一点在路径头部,并且路径总容积满足车辆容积限制
209      * 先单独判断 是为了防止 已经分配了车辆的路径没有充分负载
210      * @param savedDistance
211      */
212     private void mergeSavedDistance(SavedDistance savedDistance) throws Exception {
213         Route r1 = savedDistance.getP1().getCurrentRoute();
214         Route r2 = savedDistance.getP2().getCurrentRoute();
215         PostOffice p1 = savedDistance.getP1();
216         PostOffice p2 = savedDistance.getP2();
217 
218         if (r1.equals(r2)) return;
219 
220         if (r1.getCapacity() != 0 ) {
221             //如果r1已分配车辆, 计算 容积限制
222             tryMergeToRoute(savedDistance, r1, r2, p1, p2);
223             return;
224         }
225 
226         if (r2.getCapacity() != 0) {
227             //如果r2已分配车辆,计算 容积限制
228             tryMergeToRoute(savedDistance, r2, r1, p2, p1);
229             return;
230         }
231 
232         //如果都没有分配过车辆, 给r1分配 目前容积最大的车辆
233         if (r1.getCapacity() == 0) {
234             if (vehicleCapacityList.isEmpty()) throw new Exception("汽车已经分配完了");
235             //设置车辆总容积
236             Float capacity = vehicleCapacityList.pop();
237             usedVehicleList.add(capacity);
238             r1.setCapacity(capacity * FACTOR);
239 
240             tryMergeToRoute(savedDistance, r1, r2, p1, p2);
241             return;
242         }
243 
244         //超过r1容积限制,尝试r2。如果没有分配过车辆, 给r2分配 目前容积最大的车辆
245         if (r2.getCapacity() == 0) {
246             if (vehicleCapacityList.isEmpty()) throw new Exception("汽车已经分配完了");
247             //设置车辆总容积
248             Float capacity = vehicleCapacityList.pop();
249             usedVehicleList.add(capacity);
250             r2.setCapacity(capacity * FACTOR);
251 
252             tryMergeToRoute(savedDistance, r2, r1, p2, p1);
253         }
254     }
255 
256     private void tryMergeToRoute(SavedDistance savedDistance,
257                                  Route existRoute,
258                                  Route mergedRoute,
259                                  PostOffice existNode,
260                                  PostOffice mergedNode) throws Exception {
261         if (appendMergedRoute(existRoute, mergedRoute, existNode,
262                 mergedNode)) {
263             if (capacityFeasible(existRoute, mergedRoute, false)) {
264                 //合并到现有路径之后
265                 if (mergedRoute.hardTimeWindowFeasible(existNode)) {
266                     mergeRoute(existRoute, mergedRoute, savedDistance
267                             , existNode, false);
268                 }
269             }
270         } else if (insertMergedRoute(existRoute, mergedRoute,
271                 existNode, mergedNode)) {
272             if (capacityFeasible(existRoute, mergedRoute, true)) {
273                 //合并到现有路径之前
274                 if (existRoute.hardTimeWindowFeasible(mergedNode)) {
275                     mergeRoute(existRoute, mergedRoute, savedDistance
276                             , existNode, true);
277                 }
278             }
279         }
280     }
281 
282     private boolean insertMergedRoute(Route existRoute,
283                                       Route mergedRoute,
284                                       PostOffice existNode,
285                                       PostOffice mergedNode) throws Exception {
286         if (mergedRoute.getNodes().size() < 3 || existRoute.getNodes().size() < 3)
287             throw new Exception("合并路径 节点少于3个");
288         return existRoute.getNodes().indexOf(existNode) == 1 && mergedRoute.getNodes().indexOf(mergedNode) == mergedRoute.getNodes().size() - 2;
289     }
290 
291     private boolean appendMergedRoute(Route existRoute,
292                                       Route mergedRoute,
293                                       PostOffice existNode,
294                                       PostOffice mergedNode) throws Exception {
295         if (mergedRoute.getNodes().size() < 3 || existRoute.getNodes().size() < 3)
296             throw new Exception("合并路径 节点少于3个");
297         return existRoute.getNodes().indexOf(existNode) == existRoute.getNodes().size() - 2 && mergedRoute.getNodes().indexOf(mergedNode) == 1;
298     }
299 
300     private boolean capacityFeasible(Route existRoute,
301                                      Route mergedRoute,
302                                      boolean isInsert) throws Exception {
303         if (existRoute.getCapacity() > (mergedRoute.getTotalReceive() + existRoute.getTotalReceive()) ) {
304             if (isInsert) {
305                 Float curLoad = mergedRoute.getTotalSendOut() + existRoute.getTotalReceive();
306                 for (PostOffice node : existRoute.getNodes()) {
307                     if (curLoad - node.getReceive() + node.getSendOut() > existRoute.getCapacity()) {
308                         return false;
309                     }
310                     curLoad = curLoad - node.getReceive() + node.getSendOut();
311                     if (curLoad < 0)
312                         throw new Exception("isInsert=true, 当前载重出错,小于0");
313                 }
314             } else {
315                 Float curLoad = existRoute.getTotalSendOut() + mergedRoute.getTotalReceive();
316                 for (PostOffice node : mergedRoute.getNodes()) {
317                     if (curLoad - node.getReceive() + node.getSendOut() > existRoute.getCapacity()) {
318                         return false;
319                     }
320                     curLoad = curLoad - node.getReceive() + node.getSendOut();
321                     if (curLoad < 0)
322                         throw new Exception("isInsert=false, 当前载重出错,小于0");
323                 }
324             }
325             return true;
326         }
327 
328         return false;
329     }
330 
331     /**
332      * 合并路径 算法
333      * @param existRoute
334      * @param mergedRoute
335      * @param savedDistance
336      * @param p
337      * @param beforeP
338      * @throws Exception
339      */
340     private void mergeRoute(Route existRoute, Route mergedRoute,
341                             SavedDistance savedDistance, PostOffice p
342             , Boolean beforeP) throws Exception {
343         //合并点在p1之前
344         LinkedList<PostOffice> mergedNodes = mergedRoute.getNodes();
345         mergedNodes.removeFirst();
346         mergedNodes.removeLast();
347 
348         //从合并处 插入 被合并路径中所有营业点
349         existRoute.getNodes().addAll(existRoute.getNodes().indexOf(p) + (beforeP ? 0 : 1), mergedRoute.getNodes());
350         //更新 原有路径上所有营业点 所在路径
351         mergedNodes.forEach(node -> {
352             node.setCurrentRoute(existRoute);
353         });
354         //更新原路径上点的前后关系
355         existRoute.updateNodeTracing();
356         //更新到达时间
357         existRoute.updateArrivedTime();
358         //更新载重
359         existRoute.addReceive(mergedRoute.getTotalReceive());
360         existRoute.addSendOut(mergedRoute.getTotalSendOut());
361         //更新路径长度
362         existRoute.setLength(existRoute.calcLength(existRoute.getNodes()));
363         //清除 被合并路径
364         if (mergedRoute.getCapacity() != 0f) {
365             vehicleCapacityList.push(mergedRoute.getCapacity() / FACTOR);
366             vehicleCapacityList.sort(Comparator.reverseOrder());
367             usedVehicleList.remove(mergedRoute.getCapacity() / FACTOR);
368         }
369         routeList.remove(mergedRoute);
370     }
371 
372 
373 
374     private void printGeneral() {
375         System.out.println("车辆总数: " + vehicleNumber);
376         System.out.println("邮局总数: " + totalPointNumber);
377         System.out.println("使用车辆: " + usedVehicleList);
378         System.out.println("剩余车辆: " + vehicleCapacityList);
379 //        System.out.println("邮局位置: " + postOfficeList);
380 //        if (savingList.size() >= 5) {
381 //            System.out.println("\n节约距离 top 5: " + savingList.subList(0, 5).toString());
382 //        }
383     }
384 
385     private void printRoute() {
386         System.out.println("\n路径: ");
387         for (int i = 0; i < routeList.size(); i++) {
388             Route r = routeList.get(i);
389             System.out.println(i + " " + r.toString());
390         }
391     }
392 
393     private void printTimeSchedule() {
394         System.out.println("\n到达时间 ");
395         for (int i = 0; i < routeList.size(); i++) {
396             Route r = routeList.get(i);
397             System.out.println(i + " " + r.timeSchedule());
398         }
399     }
400 
401 
402     public int getTotalPointNumber() {
403         return totalPointNumber;
404     }
405 
406 
407     public LinkedList<Float> getUsedVehicleList() {
408         return usedVehicleList;
409     }
410 
411     public List<PostOffice> getPostOfficeList() {
412         return postOfficeList;
413     }
414 
415     public List<Route> getRouteList() {
416         return routeList;
417     }
418 
419 }
程序入口

 

测试数据

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
测试数据

 

转载于:https://www.cnblogs.com/andrew-chen/p/11498871.html

正文到此结束
本文目录