1. 程式人生 > >基於C-W節約演算法的車輛路徑規劃問題的Java實現

基於C-W節約演算法的車輛路徑規劃問題的Java實現

 

  • 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每小時。試用最少郵車,並規劃郵車的行駛路線使總費用最省。

 

 

那麼輸入引數需要包括:

  1. 各個節點的經緯度,郵件收寄數,郵件送達數,時間窗(如果有要求的話,包括最早、最晚到達時間),裝卸貨時間
  2. 可用車輛的載重

 

輸出結果就是演算法形成的路徑,每條路徑中包括到達郵局的先後次序。

 

 

 

目前已經基於CW節約演算法,實現 載重量 約束 以及 時間視窗約束,使用Java作為實現。

 

郵局類

  1 package vrp;
  2 
  3 import java.util.Objects;
  4 
  5 /**
  6  * @author <a herf="[email protected]">陳海越</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="[email protected]">陳海越</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="[email protected]">陳海越</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="[email protected]">陳海越</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
測試資料

&n