基於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每小時。試用最少郵車,並規劃郵車的行駛路線使總費用最省。
那麼輸入引數需要包括:
- 各個節點的經緯度,郵件收寄數,郵件送達數,時間窗(如果有要求的話,包括最早、最晚到達時間),裝卸貨時間
- 可用車輛的載重
輸出結果就是演算法形成的路徑,每條路徑中包括到達郵局的先後次序。
目前已經基於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