车辆路线问题(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作为实现。

邮局类

 package vrp;

 import java.util.Objects;

 /**
* @author <a herf="chenhy@itek-china.com">陈海越</a>
* @version 1.0
* @since 新标准版5.0
*/
public class PostOffice implements Cloneable { public PostOffice(int index, String name, float x, float y,
float receive, float sendOut,
int earliestTime, int latestTime, int duration,
int type) {
this.index = index;
this.name = name;
this.x = x;
this.y = y;
this.receive = receive;
this.sendOut = sendOut;
this.earliestTime = earliestTime;
this.latestTime = latestTime;
this.duration = duration;
this.type = type;
} /**
* 序号
*/
private int index; private String name; private float x; private float y; private float receive; private float sendOut; /**
* 最早到达时间
*/
private int earliestTime; /**
* 最晚到达时间
*/
private int latestTime; /**
* 到达时间
*/
private int arrivedTime; private int duration; private int type; private Route currentRoute; private PostOffice previousNode; private PostOffice nextNode; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public float getSendOut() {
return sendOut;
} public void setSendOut(float sendOut) {
this.sendOut = sendOut;
} public PostOffice getPreviousNode() {
return previousNode;
} public void setPreviousNode(PostOffice previousNode) {
this.previousNode = previousNode;
} public PostOffice getNextNode() {
return nextNode;
} public void setNextNode(PostOffice nextNode) {
this.nextNode = nextNode;
} public int getArrivedTime() {
return arrivedTime;
} public void setArrivedTime(int arrivedTime) {
this.arrivedTime = arrivedTime;
} public Route getCurrentRoute() {
return currentRoute;
} public void setCurrentRoute(Route currentRoute) {
this.currentRoute = currentRoute;
} public int getIndex() {
return index;
} public float getX() {
return x;
} public float getY() {
return y;
} public float getReceive() {
return receive;
} public int getEarliestTime() {
return earliestTime;
} public int getLatestTime() {
return latestTime;
} public int getDuration() {
return duration;
} public int getType() {
return type;
} public float distanceTo(PostOffice p2) {
return distanceTo(y, x, p2.y, p2.x, 'K');
} /**
* 使用经纬度计算,返回距离
* @param lat1 纬度1
* @param lon1 经度1
* @param lat2 纬度2
* @param lon2 经度2
* @param unit 'K' 公里 ,默认 英里
* @return
*/
private float distanceTo(double lat1, double lon1, double lat2, double lon2, char unit) {
double theta = lon1 - lon2;
double dist = Math.sin(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(deg2rad(theta));
dist = Math.acos(dist);
dist = rad2deg(dist);
dist = dist * 60 * 1.1515;
if (unit == 'K') {
dist = dist * 1.609344;
}
return (float)(dist);
} private double deg2rad(double deg) {
return (deg * Math.PI / 180.0);
} private double rad2deg(double rad) {
return (rad * 180.0 / Math.PI);
} public int getDepartTime() {
return arrivedTime + duration;
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PostOffice that = (PostOffice) o;
return Float.compare(that.x, x) == 0 &&
Float.compare(that.y, y) == 0;
} @Override
public String toString() {
return "PostOffice{" + index +
" (" + x +
", " + y +
")}";
} @Override
public Object clone() throws CloneNotSupportedException {
PostOffice clone = (PostOffice) super.clone();
clone.setCurrentRoute(currentRoute == null ? null :
(Route) currentRoute.clone());
return clone;
} public String getTimeInterval() {
return index + " [到达时间:" + convertHHmm(arrivedTime) +
", 出发时间:" + convertHHmm(getDepartTime()) +
"]";
} public String convertHHmm(int mins) {
return (mins < 60 ? "0:" : mins/60 + ":") + mins%60 + "";
} public String getCoordinate() {
return index + " [" + y + ", " + x + "]";
} @Override
public int hashCode() {
return Objects.hash(x, y);
}
}

PostOffice.java

路径类

 package vrp;

 import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors; /**
* @author <a herf="chenhy@itek-china.com">陈海越</a>
* @version 1.0
* @since 新标准版5.0
*
* <pre>
* 历史:
* 建立: 2019/9/3 陈海越
* </pre>
*/
public class Route implements Cloneable{ public static final double DEFAULT_DELTA = 0.0001;
private LinkedList<PostOffice> nodes; private Float capacity = 0f; private Float totalReceive = 0f; private Float totalSendOut = 0f; private Float length = 0f; /**
* 公里每分钟
*/
private Float speed = 0.5f; public void setNodesAndUpdateLoad(List<PostOffice> nodes) {
this.nodes = new LinkedList<>(nodes);
for (int i = 0; i < nodes.size(); i++) {
PostOffice node = nodes.get(i);
addReceive(node.getReceive());
addSendOut(node.getSendOut());
}
} public void setCapacity(Float capacity) {
this.capacity = capacity;
} public Float getSpeed() {
return speed;
} public void setSpeed(Float speed) {
this.speed = speed;
} public void setTotalReceive(Float totalReceive) {
this.totalReceive = totalReceive;
} public Float getTotalReceive() {
return totalReceive;
} public Float getTotalSendOut() {
return totalSendOut;
} public void setTotalSendOut(Float totalSendOut) {
this.totalSendOut = totalSendOut;
} public Float getCapacity() {
return capacity;
} public LinkedList<PostOffice> getNodes() {
return nodes;
} public Float getLength() {
return length;
} public void setLength(Float length) {
this.length = length;
} public void addReceive(Float receive) {
totalReceive += receive;
} public void addSendOut(Float sendOut) {
totalSendOut += sendOut;
} public Float calcLength(LinkedList<PostOffice> nodes) {
Float length = 0f;
if (!nodes.isEmpty()) {
PostOffice firstNode = nodes.getFirst();
for (int i=1;i<nodes.size();i++) {
PostOffice next = nodes.get(i);
length += next.distanceTo(firstNode);
firstNode = next;
}
}
return length;
} public boolean twoOptOptimise(){
//交换中间路径 任意两点,尝试优化路径
boolean optimised = false;
for (int i = 1; i < nodes.size()-1; i++) {
for (int j = i+1; j < nodes.size()-1; j++) {
LinkedList<PostOffice> tempList = (LinkedList<PostOffice>) nodes.clone();
int k = i, l = j;
while (k < l) {
Collections.swap(tempList, k, l);
k++;l--;
}
Float tempLength = calcLength(tempList);
if (length - tempLength > DEFAULT_DELTA) {
//优化成功
nodes = tempList;
length = tempLength;
updateNodeTracing();
updateArrivedTime();
optimised = true;
}
}
}
return optimised;
} /**
* 更新路径上点的前后关系
*/
public void updateNodeTracing() {
PostOffice previous = nodes.get(0);
for (int i = 1; i < nodes.size(); i++) {
PostOffice node = nodes.get(i);
//设置点的前后关系
node.setPreviousNode(previous);
previous.setNextNode(node);
previous = node;
}
} public void updateArrivedTime() {
PostOffice previous = nodes.get(0);
previous.setArrivedTime(previous.getEarliestTime());
for (int i = 1; i < nodes.size(); i++) {
PostOffice node = nodes.get(i);
// 节点到达时间为 离开上一节点时间加上路程时间
int arrivedTime = previous.getDepartTime() + (int)((node.distanceTo(previous)) / speed);
node.setArrivedTime(arrivedTime);
previous = node;
} } @Override
public String toString() {
return (nodes == null ? "[]" : nodes.stream().map(PostOffice::getCoordinate).collect(Collectors.toList()).toString()) +
", 车辆载重=" + capacity +
", 总送达=" + totalReceive +
", 总收寄=" + totalSendOut +
", 总长度=" + length + "公里" +
", 速度=" + speed * 60 + "公里每小时";
} public String timeSchedule() {
return "到达时间{" +
"邮局=" + (nodes == null ? "[]" : nodes.stream().map(PostOffice::getTimeInterval).collect(Collectors.toList()).toString());
} @Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
} /**
* 硬时间窗限制
* 到达时间 不能早于最早时间,不能晚于最晚时间
* @param p1
* @return
*/
public boolean hardTimeWindowFeasible(PostOffice p1) {
PostOffice previous = p1;
int lastDepart = previous.getDepartTime();
for (PostOffice node : nodes) {
int arrivedTime = lastDepart + (int)((node.distanceTo(previous)) / speed);
if (arrivedTime < node.getEarliestTime() || arrivedTime > node.getLatestTime() ) {
return false;
}
lastDepart = arrivedTime + node.getDuration();
previous = node;
}
return true;
} public boolean vehicleOptimise(LinkedList<Float> vehicleCapacityList, LinkedList<Float> usedVehicleList) { vehicleCapacityList.sort(Comparator.naturalOrder());
for (Float temp : vehicleCapacityList) {
if (temp < this.capacity) {
if (temp > this.totalReceive) {
Float curLoad = totalReceive;
boolean cando = true;
for (PostOffice node : nodes) {
if ( curLoad - node.getReceive() + node.getSendOut() > temp) {
cando = false;
break;
}
curLoad = curLoad - node.getReceive() + node.getSendOut();
}
if (cando) {
vehicleCapacityList.remove(temp);
vehicleCapacityList.add(capacity);
usedVehicleList.remove(capacity);
usedVehicleList.add(temp);
this.capacity = temp;
return true;
}
} }
}
return false;
}
}

Route.java

节约距离类

 package vrp;

 /**
* @author <a herf="chenhy@itek-china.com">陈海越</a>
* @version 1.0
* @since 新标准版5.0
*
* <pre>
* 历史:
* 建立: 2019/9/3 陈海越
* </pre>
*/
public class SavedDistance implements Comparable { private PostOffice p1;
private PostOffice p2;
private float savedDistance; public SavedDistance(PostOffice p1, PostOffice p2, float savedDistance) {
this.p1 = p1;
this.p2 = p2;
this.savedDistance = savedDistance;
} public PostOffice getP1() {
return p1;
} public PostOffice getP2() {
return p2;
} public PostOffice getAnother(PostOffice p) {
if (p.equals(p1)) {
return p2;
} else if (p.equals(p2)) {
return p1;
}
return null;
} public float getSavedDistance() {
return savedDistance;
} @Override
public String toString() {
return "SD{" +
"(" + p1 +
" -> " + p2 +
"), saved=" + savedDistance +
'}';
} @Override
public int compareTo(Object o) {
return Float.compare(savedDistance, ((SavedDistance) o).savedDistance);
} public PostOffice nodeAt(Route existRoute) throws Exception {
if (existRoute.getNodes().contains(p1)) {
return p1;
} else if (existRoute.getNodes().contains(p2)) {
return p2;
} throw new Exception("p1:" + p1 + ", p2:" + p2 +". 均不存在于路径:" + existRoute);
}
}

SaveDistance.java

程序入口

 package vrp;

 import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List; /**
* @author <a herf="chenhy@itek-china.com">陈海越</a>
* @version 1.0
* @since 新标准版5.0
*
* <pre>
* 历史:
* 建立: 2019/9/2 陈海越
* </pre>
*/
public class VRPTest { public static final String KONGGE = "\\s+|\r";
public static final int FACTOR = 1;
private int vehicleNumber;
private int totalPointNumber;
private LinkedList<Float> vehicleCapacityList = new LinkedList<>();
private LinkedList<Float> usedVehicleList = new LinkedList<>();
private List<PostOffice> postOfficeList = new ArrayList<>();
private List<Route> routeList = new ArrayList<>();
private float[][] distMatrix;
private List<SavedDistance> savingList = new ArrayList<>(); public static void main(String[] args) throws Exception {
VRPTest vrpTest = new VRPTest();
vrpTest.readFromFile("C:\\Users\\Administrator\\Documents\\vrp_data\\test3.txt");
vrpTest.vrp();
} /**
* 从文件中读取数据
*/
public void readFromFile(String fileName) {
File file = new File(fileName);
try {
BufferedReader br = new BufferedReader(new FileReader(
file));
constructGeneral(br);
constructVehicle(br);
constructNodes(br);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} private void constructGeneral(BufferedReader br) throws IOException {
String first = br.readLine().trim();
String[] firstLineArr = first.split(KONGGE);
vehicleNumber = Integer.parseInt(firstLineArr[0]);
totalPointNumber = Integer.parseInt(firstLineArr[1]);
} private void constructVehicle(BufferedReader br) throws IOException {
String vehicleCapacity = br.readLine().trim();
for (String s : vehicleCapacity.split(KONGGE)) {
vehicleCapacityList.add(Float.parseFloat(s));
}
} private void constructNodes(BufferedReader br) throws IOException {
for (int i = 0; i < totalPointNumber; i++) {
String postStr = br.readLine().trim();
String[] postArr = postStr.split(KONGGE);
PostOffice postOffice =
new PostOffice(Integer.parseInt(postArr[0]), postArr[1],
Float.parseFloat(postArr[2]),
Float.parseFloat(postArr[3]),
Float.parseFloat(postArr[4]),
Float.parseFloat(postArr[5]),
Integer.parseInt(postArr[6]),
Integer.parseInt(postArr[7]),
Integer.parseInt(postArr[8]),
isDepot(i));
postOfficeList.add(postOffice);
}
} private int isDepot(int i) {
//第一条记录为仓库
return i == 0 ? 0 : 1;
} public void vrp() throws Exception {
calcDistMatrix();
calcSavingMatrix();
calcRoute();
cwSaving();
//optimise
twoOptOptimise();
capacityOptimise(); printGeneral();
printRoute();
// printTimeSchedule();
} /**
* 计算距离矩阵
*/
private void calcDistMatrix() {
int length = postOfficeList.size();
distMatrix = new float[length][length];
for (int i = 0; i < totalPointNumber; i++) {
for (int j = 0; j < i; j++) {
distMatrix[i][j] = postOfficeList.get(i).distanceTo(postOfficeList.get(j));
distMatrix[j][i] = distMatrix[i][j];
}
distMatrix[i][i] = 0;
}
} /**
* 计算节约距离列表
*/
private void calcSavingMatrix() {
for (int i = 2; i < totalPointNumber; i++) {
for (int j = 1; j < i; j++) {
PostOffice pi = postOfficeList.get(i);
PostOffice pj = postOfficeList.get(j);
PostOffice depot = postOfficeList.get(0);
float dist = pi.distanceTo(pj);
float saving =
pi.distanceTo(depot) + pj.distanceTo(depot) - dist;
savingList.add(new SavedDistance(postOfficeList.get(i), postOfficeList.get(j), saving));
}
}
savingList.sort(Collections.reverseOrder());
} private boolean twoOptOptimise() {
for (Route route : routeList) {
if (route.twoOptOptimise()) {
return true;
}
}
return false;
} private boolean capacityOptimise() {
for (Route route : routeList) {
if (route.vehicleOptimise(vehicleCapacityList, usedVehicleList)) {
return true;
}
}
return false;
} /**
* 构建基础路径
*/
private void calcRoute() throws CloneNotSupportedException {
//将所有点单独与集散中心组成一条路径,路径对象中包含集散中心
PostOffice depot = postOfficeList.get(0);
for(int i = 1 ; i<postOfficeList.size(); i++) {
Route r = new Route();
//更新点 所在路径
PostOffice startNode = (PostOffice) depot.clone();
startNode.setCurrentRoute(r);
PostOffice endNode = (PostOffice) depot.clone();
endNode.setCurrentRoute(r);
postOfficeList.get(i).setCurrentRoute(r);
//更新路径 上的点
r.setNodesAndUpdateLoad(new LinkedList<>(Arrays.asList(startNode, postOfficeList.get(i), endNode))); //更新到达时间
r.updateArrivedTime();
//更新路径长度
r.setLength(r.calcLength(r.getNodes()));
//更新原路径上点的前后关系
r.updateNodeTracing();
//更新载重
routeList.add(r);
}
} /**
* CW节约算法构建路程
* @throws Exception
*/
private void cwSaving() throws Exception {
//取出save值最大的路径,尝试加入当前路径
for (SavedDistance savedDistance : savingList) {
mergeSavedDistance(savedDistance);
}
} /**
* 合并路径规则:
* 两点中有一点在路径尾部,一点在路径头部,并且路径总容积满足车辆容积限制
* 先单独判断 是为了防止 已经分配了车辆的路径没有充分负载
* @param savedDistance
*/
private void mergeSavedDistance(SavedDistance savedDistance) throws Exception {
Route r1 = savedDistance.getP1().getCurrentRoute();
Route r2 = savedDistance.getP2().getCurrentRoute();
PostOffice p1 = savedDistance.getP1();
PostOffice p2 = savedDistance.getP2(); if (r1.equals(r2)) return; if (r1.getCapacity() != 0 ) {
//如果r1已分配车辆, 计算 容积限制
tryMergeToRoute(savedDistance, r1, r2, p1, p2);
return;
} if (r2.getCapacity() != 0) {
//如果r2已分配车辆,计算 容积限制
tryMergeToRoute(savedDistance, r2, r1, p2, p1);
return;
} //如果都没有分配过车辆, 给r1分配 目前容积最大的车辆
if (r1.getCapacity() == 0) {
if (vehicleCapacityList.isEmpty()) throw new Exception("汽车已经分配完了");
//设置车辆总容积
Float capacity = vehicleCapacityList.pop();
usedVehicleList.add(capacity);
r1.setCapacity(capacity * FACTOR); tryMergeToRoute(savedDistance, r1, r2, p1, p2);
return;
} //超过r1容积限制,尝试r2。如果没有分配过车辆, 给r2分配 目前容积最大的车辆
if (r2.getCapacity() == 0) {
if (vehicleCapacityList.isEmpty()) throw new Exception("汽车已经分配完了");
//设置车辆总容积
Float capacity = vehicleCapacityList.pop();
usedVehicleList.add(capacity);
r2.setCapacity(capacity * FACTOR); tryMergeToRoute(savedDistance, r2, r1, p2, p1);
}
} private void tryMergeToRoute(SavedDistance savedDistance,
Route existRoute,
Route mergedRoute,
PostOffice existNode,
PostOffice mergedNode) throws Exception {
if (appendMergedRoute(existRoute, mergedRoute, existNode,
mergedNode)) {
if (capacityFeasible(existRoute, mergedRoute, false)) {
//合并到现有路径之后
if (mergedRoute.hardTimeWindowFeasible(existNode)) {
mergeRoute(existRoute, mergedRoute, savedDistance
, existNode, false);
}
}
} else if (insertMergedRoute(existRoute, mergedRoute,
existNode, mergedNode)) {
if (capacityFeasible(existRoute, mergedRoute, true)) {
//合并到现有路径之前
if (existRoute.hardTimeWindowFeasible(mergedNode)) {
mergeRoute(existRoute, mergedRoute, savedDistance
, existNode, true);
}
}
}
} private boolean insertMergedRoute(Route existRoute,
Route mergedRoute,
PostOffice existNode,
PostOffice mergedNode) throws Exception {
if (mergedRoute.getNodes().size() < 3 || existRoute.getNodes().size() < 3)
throw new Exception("合并路径 节点少于3个");
return existRoute.getNodes().indexOf(existNode) == 1 && mergedRoute.getNodes().indexOf(mergedNode) == mergedRoute.getNodes().size() - 2;
} private boolean appendMergedRoute(Route existRoute,
Route mergedRoute,
PostOffice existNode,
PostOffice mergedNode) throws Exception {
if (mergedRoute.getNodes().size() < 3 || existRoute.getNodes().size() < 3)
throw new Exception("合并路径 节点少于3个");
return existRoute.getNodes().indexOf(existNode) == existRoute.getNodes().size() - 2 && mergedRoute.getNodes().indexOf(mergedNode) == 1;
} private boolean capacityFeasible(Route existRoute,
Route mergedRoute,
boolean isInsert) throws Exception {
if (existRoute.getCapacity() > (mergedRoute.getTotalReceive() + existRoute.getTotalReceive()) ) {
if (isInsert) {
Float curLoad = mergedRoute.getTotalSendOut() + existRoute.getTotalReceive();
for (PostOffice node : existRoute.getNodes()) {
if (curLoad - node.getReceive() + node.getSendOut() > existRoute.getCapacity()) {
return false;
}
curLoad = curLoad - node.getReceive() + node.getSendOut();
if (curLoad < 0)
throw new Exception("isInsert=true, 当前载重出错,小于0");
}
} else {
Float curLoad = existRoute.getTotalSendOut() + mergedRoute.getTotalReceive();
for (PostOffice node : mergedRoute.getNodes()) {
if (curLoad - node.getReceive() + node.getSendOut() > existRoute.getCapacity()) {
return false;
}
curLoad = curLoad - node.getReceive() + node.getSendOut();
if (curLoad < 0)
throw new Exception("isInsert=false, 当前载重出错,小于0");
}
}
return true;
} return false;
} /**
* 合并路径 算法
* @param existRoute
* @param mergedRoute
* @param savedDistance
* @param p
* @param beforeP
* @throws Exception
*/
private void mergeRoute(Route existRoute, Route mergedRoute,
SavedDistance savedDistance, PostOffice p
, Boolean beforeP) throws Exception {
//合并点在p1之前
LinkedList<PostOffice> mergedNodes = mergedRoute.getNodes();
mergedNodes.removeFirst();
mergedNodes.removeLast(); //从合并处 插入 被合并路径中所有营业点
existRoute.getNodes().addAll(existRoute.getNodes().indexOf(p) + (beforeP ? 0 : 1), mergedRoute.getNodes());
//更新 原有路径上所有营业点 所在路径
mergedNodes.forEach(node -> {
node.setCurrentRoute(existRoute);
});
//更新原路径上点的前后关系
existRoute.updateNodeTracing();
//更新到达时间
existRoute.updateArrivedTime();
//更新载重
existRoute.addReceive(mergedRoute.getTotalReceive());
existRoute.addSendOut(mergedRoute.getTotalSendOut());
//更新路径长度
existRoute.setLength(existRoute.calcLength(existRoute.getNodes()));
//清除 被合并路径
if (mergedRoute.getCapacity() != 0f) {
vehicleCapacityList.push(mergedRoute.getCapacity() / FACTOR);
vehicleCapacityList.sort(Comparator.reverseOrder());
usedVehicleList.remove(mergedRoute.getCapacity() / FACTOR);
}
routeList.remove(mergedRoute);
} private void printGeneral() {
System.out.println("车辆总数: " + vehicleNumber);
System.out.println("邮局总数: " + totalPointNumber);
System.out.println("使用车辆: " + usedVehicleList);
System.out.println("剩余车辆: " + vehicleCapacityList);
// System.out.println("邮局位置: " + postOfficeList);
// if (savingList.size() >= 5) {
// System.out.println("\n节约距离 top 5: " + savingList.subList(0, 5).toString());
// }
} private void printRoute() {
System.out.println("\n路径: ");
for (int i = 0; i < routeList.size(); i++) {
Route r = routeList.get(i);
System.out.println(i + " " + r.toString());
}
} private void printTimeSchedule() {
System.out.println("\n到达时间 ");
for (int i = 0; i < routeList.size(); i++) {
Route r = routeList.get(i);
System.out.println(i + " " + r.timeSchedule());
}
} public int getTotalPointNumber() {
return totalPointNumber;
} public LinkedList<Float> getUsedVehicleList() {
return usedVehicleList;
} public List<PostOffice> getPostOfficeList() {
return postOfficeList;
} public List<Route> getRouteList() {
return routeList;
} }

程序入口

测试数据

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

测试数据

基于C-W节约算法的车辆路径规划问题的Java实现的更多相关文章

  1. [python] A*算法基于栅格地图的全局路径规划

    # 所有节点的g值并没有初始化为无穷大 # 当两个子节点的f值一样时,程序选择最先搜索到的一个作为父节点加入closed # 对相同数值的不同对待,导致不同版本的A*算法找到等长的不同路径 # 最后c ...

  2. DWA局部路径规划算法论文阅读:The Dynamic Window Approach to Collision Avoidance。

    DWA(动态窗口)算法是用于局部路径规划的算法,已经在ROS中实现,在move_base堆栈中:http://wiki.ros.org/dwa_local_planner DWA算法第一次提出应该是1 ...

  3. 基于谷歌地图的Dijkstra算法水路路径规划

    最终效果图如下: 还是图.邻接表,可以模拟出几个对象=>节点.边.路径.三个类分别如下: Node 节点: using System; using System.Collections.Gene ...

  4. 最小生成树--Prim算法,基于优先队列的Prim算法,Kruskal算法,Boruvka算法,“等价类”UnionFind

    最小支撑树树--Prim算法,基于优先队列的Prim算法,Kruskal算法,Boruvka算法,“等价类”UnionFind 最小支撑树树 前几节中介绍的算法都是针对无权图的,本节将介绍带权图的最小 ...

  5. 基于ReliefF和K-means算法的医学应用实例

    基于ReliefF和K-means算法的医学应用实例 数据挖掘方法的提出,让人们有能力最终认识数据的真正价值,即蕴藏在数据中的信息和知识.数据挖掘 (DataMiriing),指的是从大型数据库或数据 ...

  6. RRT路径规划算法

    传统的路径规划算法有人工势场法.模糊规则法.遗传算法.神经网络.模拟退火算法.蚁群优化算法等.但这些方法都需要在一个确定的空间内对障碍物进行建模,计算复杂度与机器人自由度呈指数关系,不适合解决多自由度 ...

  7. PRM路径规划算法

    路径规划作为机器人完成各种任务的基础,一直是研究的热点.研究人员提出了许多规划方法:如人工势场法.单元分解法.随机路标图(PRM)法.快速搜索树(RRT)法等.传统的人工势场.单元分解法需要对空间中的 ...

  8. 基于Qt的A*算法可视化分析

    代码地址如下:http://www.demodashi.com/demo/13677.html 需求 之前做过一个无人车需要自主寻找最佳路径,所以研究了相关的寻路算法,最终选择A算法,因为其简单易懂, ...

  9. 路径规划: PRM 路径规划算法 (Probabilistic Roadmaps 随机路标图)

    随机路标图-Probabilistic Roadmaps (路径规划算法) 路径规划作为机器人完成各种任务的基础,一直是研究的热点.研究人员提出了许多规划方法如: 1. A* 2. Djstar 3. ...

随机推荐

  1. 什么?小程序实时语音识别你还在痛苦的对接科大讯飞?百度Ai识别?

    前言 微信小程序,说不上大火,但是需求还是不少的.各大企业都想插一足 于是前端同学就有事情做了. 需求 我需要录音 我边说话边识别,我要同声传译,我要文字转语音,还要萝莉音 我:??? 正文 一开始, ...

  2. 【JDK】JDK源码分析-ReentrantLock

    概述 在 JDK 1.5 以前,锁的实现只能用 synchronized 关键字:1.5 开始提供了 ReentrantLock,它是 API 层面的锁.先看下 ReentrantLock 的类签名以 ...

  3. go 学习笔记之走进Goland编辑器

    工欲善其事必先利其器,命令行工具虽然能够在一定程度上满足基本操作的需求,但实际工作中总不能一直使用命令行工具进行编码操作吧? 学习 Go 语言同样如此,为此需要寻找一个强大的 IDE 集成环境帮助我们 ...

  4. 手摸手,带你用vue实现后台管理权限系统及顶栏三级菜单显示

    手摸手,带你用vue实现后台管理权限系统及顶栏三级菜单显示 效果演示地址 项目demo展示 重要功能总结 权限功能的实现 权限路由思路: 根据用户登录的roles信息与路由中配置的roles信息进行比 ...

  5. strstr函数使用中的一个错误解决

    最近使用ESP8266的时候,联网的过程中需要使用strstr函数来读取串口发来的某些重要信息, 使用strstr函数发现某些时候能够正常返回需要寻找的字符串的指针,有些时候找不到,后来发现原来是这样 ...

  6. mvnjar包冲突解决方法

    命令 mvn dependency:tree -Dverbose 结果: [INFO] +- com.esotericsoftware:kryo:jar:4.0.2:test [INFO] | +- ...

  7. Opengl_入门学习分享和记录_番外篇00(MacOS上如何给Xcode 适配openGL)

    现在前面的废话:哇这次没有鸽太久,突然想起来还没有介绍如何适配opengl的衍生库.今天一并介绍下,同样看时间允不允许,让我再把之前学到的一些东西再次总结一遍. 正文开始: 首先大家要知道我们的Ope ...

  8. 003——Netty之Buffer、Channel以及多路复用器Selector

    Buffer 1.缓冲区类型 2.缓冲区定义 (1)Buffer是一个对象,其中包含写入与读出的数据.是新IO与原IO的重要区别.任何情况下访问NIO中的数据都需要通过缓存区进行操作. (2)Buff ...

  9. nginx配置ssl证书实现https加密请求详解

    原文链接:http://www.studyshare.cn/software/details/1175/0 一.加密方式 1.对称加密 所谓对称加密即:客户端使用一串固定的秘钥对传输内容进行加密,服务 ...

  10. 吉特日化MES-生产制造的几种形态

    1. 订货型和备货型 工厂的生产形态是以接受订单时间和开始生产时间来划分的,因为生产要么得到销售指令要么得到备货指令不能无缘无故的生产.销售指令驱动生产直接受市场销售影响,而备货型可能是对市场的一种预 ...