参考文献:
微信公众号“运筹帷幄”:构造VRP初始解的几种启发式算法
微信公众号“数据魔法师”:迭代局部搜索算法
最近邻算法
在这套代码中,原作者用最近邻算法构造初始可行解,然后用后面的局部搜索算法、禁忌搜索算法改善该初始可行解。
/*
* The MIT License (MIT)
*
* Copyright (c) 2020
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* */
package com.vrp.app.solvers;
import com.vrp.app.Solver;
import com.vrp.app.components.Node;
import com.vrp.app.components.Route;
import com.vrp.app.components.Solution;
import java.util.ArrayList;
/**
* A constructive heuristic is developed based on Nearest Neighbor to produce an initial solution for the VRP.
* It is based on the nearest neighbor heuristic for TSP. However, in this case the capacity constraints of the routes
* must be taken into consideration while inserting a new customer. If no neighbor that respects the limitations exist,
* then the current route is finalized and the method continues by building the second route and so on. Obviously the method
* terminates when all customers have been inserted into the solution.
*/
public class NearestNeighbor implements Solver {
private final int numberOfVehicles;
private final int numberOfCustomers;
private final ArrayList<Node> allNodes;
private final Node depot;
private final double[][] distanceMatrix;
private Solution solution;
public NearestNeighbor(int numberOfCustomers, int numberOfVehicles, Node depot, double[][] distanceMatrix, ArrayList<Node> allNodes) {
this.depot = depot;
this.numberOfCustomers = numberOfCustomers;
this.numberOfVehicles = numberOfVehicles;
this.distanceMatrix = distanceMatrix;
this.allNodes = allNodes;
}
@Override
public void run() {
Solution solution = new Solution();
ArrayList<Route> routes = solution.getRoute();
for (int i = 1; i <= numberOfVehicles; i++) {// 一辆车一条路径
Route route_nodes = new Route();
route_nodes.setID(i);
routes.add(route_nodes);
}
int toRoute = numberOfCustomers;
for (int j = 1; j <= numberOfVehicles; j++) {
ArrayList<Node> nodeSequence = routes.get(j - 1).getNodes();
int remaining = routes.get(j - 1).getCapacity();// 目前所余容量
int load = routes.get(j - 1).getLoad();
nodeSequence.add(depot);
boolean finalized = false;
if (toRoute == 0) {//?? what's the 'toRoute'?
finalized = true;
nodeSequence.add(depot);
}
while (finalized == false) {
int positionOfTheNextOne = -1;
double bestCostForTheNextOne = Double.MAX_VALUE;
Node lastInTheRoute = nodeSequence.get(nodeSequence.size() - 1);//当前这条路径的最后的节点
for (int k = 1; k < allNodes.size(); k++) {// k=0的点是depot
Node candidate = allNodes.get(k);// 下一个 候选节点
if (!candidate.getRouted()) {// candidate 这个节点还没有被安排到路径中
//找距离上一个点lastInTheRoute最近的点,如果满足容量约束,那么加入到路径中
double trialCost = distanceMatrix[lastInTheRoute.getId()][candidate.getId()];
if (trialCost < bestCostForTheNextOne && candidate.getDemand() <= remaining) {
positionOfTheNextOne = k;
bestCostForTheNextOne = trialCost;
}
}
}
if (positionOfTheNextOne != -1) {// found the next point!
// expand the route and update the relevant info
Node insertedNode = allNodes.get(positionOfTheNextOne);
nodeSequence.add(insertedNode);
solution.setCost(solution.getCost() + bestCostForTheNextOne);
routes.get(j - 1).setCost(routes.get(j - 1).getCost() + bestCostForTheNextOne);
insertedNode.setRouted(true); // Node 'insertedNode' has been inserted into some route
remaining = remaining - insertedNode.getDemand();// update the remaining capacity
load = load + insertedNode.getDemand(); // update its load
routes.get(j - 1).setLoad(load);
toRoute--; //成功接上一个新点
} else {// not fount the next point! 没有找到更好的邻居,于是终结该路径
nodeSequence.add(depot);
solution.setCost(solution.getCost() + distanceMatrix[lastInTheRoute.getId()][0]);
routes.get(j - 1).setCost(routes.get(j - 1).getCost() + distanceMatrix[lastInTheRoute.getId()][0]);
finalized = true;
}
}
}
setSolution(solution);
}//end run func
@Override
public void setSolution(Solution solution) {
this.solution = solution;
}
@Override
public Solution getSolution() {
return solution;
}
}
局部搜索算法
它是一种贪心搜索算法,从初始解出发,找到它所在邻域内更好的解,然后去到更优的解,继续搜索邻域中更好的解,直到邻域中没有更好的解(即局部最优解)时终止算法。
不同局部搜索算法有不同的邻域动作(怎么定义“邻居”或者说 如何产生邻居)、不同的邻居解选择策略。
- 分类
局部搜索算法有简单局部搜索算法(如模拟退火、禁忌搜索算法等)、迭代局部搜索算法。 - 迭代局部搜索算法的步骤
从初始解出发,搜索其邻域得到更好的解x;
然后,对其做扰动,得到解x1;
接着,在解x1的邻域中,搜索更好的解,得解x2;
将解2作为下一步迭代的当前解。
这套代码中,给了两个版本的局部搜索算法,
第一种是只能在一条路径上做relocation(删除一个节点,然后选该路径上的其他位置插入);
第二种是在两条路径上完成relocation(在一个路径上删除一个节点,在另一个路径上的某个位置插入该节点);// See class LocalSearchIntraAndInterRelocation
代码的逻辑是:先找到最好的relocation(见函数 findBestRelocationMove ),然后将这个relocation作用到当前解上,并在末尾更新当前解(见函数 applyRelocationMove)。
第一种:
package com.vrp.app.solvers;
import com.vrp.app.Solver;
import com.vrp.app.components.Node;
import com.vrp.app.components.RelocationMove;
import com.vrp.app.components.Solution;
import java.util.ArrayList;
/**
* A local search is then designed for improving the initial solution generated at Component 3.
* This local search method considers all possible customer relocations within their routes.
* The relocation yielding the biggest cost reduction is selected to be applied to the candidate solution.
* The method terminates if no improving intra-route relocation can be identified.
*/
public class LocalSearchIntraRelocation implements Solver {
private final int numberOfVehicles;
private final int numberOfCustomers;
private final ArrayList<Node> allNodes;
private final Node depot;
private final double[][] distanceMatrix;
private Solution solution;// 当前解
// ??为什么final成员数据,可以用构造函数初始化?
public LocalSearchIntraRelocation(int numberOfCustomers, int numberOfVehicles, Node depot, double[][] distanceMatrix, ArrayList<Node> allNodes) {
this.depot = depot;
this.numberOfCustomers = numberOfCustomers;
this.numberOfVehicles = numberOfVehicles;
this.distanceMatrix = distanceMatrix;
this.allNodes = allNodes;
}
@Override
public void run() {
boolean terminationCondition = false;
int localSearchIterator = 0;
RelocationMove relocationMove = new RelocationMove(
-1, -1, 0, Double.MAX_VALUE);
while (!terminationCondition) {
findBestRelocationMove(relocationMove, solution, distanceMatrix, numberOfVehicles);
if (relocationMove.getMoveCost() < 0) {
applyRelocationMove(relocationMove, solution, distanceMatrix);
localSearchIterator = localSearchIterator + 1;
} else { // 没有找到更好的relocation,那就终止
terminationCondition = true;
}
}
}
@Override
public Solution getSolution() {
return solution;
}
@Override
public void setSolution(Solution solution) {
this.solution = solution;
}
private void applyRelocationMove(RelocationMove relocationMove, Solution currentSolution, double[][] distanceMatrix) {
Node relocatedNode = currentSolution.getRoute().get(relocationMove.getRoute()).getNodes().get(relocationMove.getPositionOfRelocated());
currentSolution.getRoute().get(relocationMove.getRoute()).getNodes().remove(relocationMove.getPositionOfRelocated());
if (relocationMove.getPositionToBeInserted() < relocationMove.getPositionOfRelocated()) {
// 拿下来的节点 是 插在靠前面的位置positionToBeInserted
// ?? why do this?
currentSolution.getRoute().get(relocationMove.getRoute()).getNodes().add(relocationMove.getPositionToBeInserted() + 1, relocatedNode);
} else {// 拿下来的节点 是 插在靠后面的位置positionOfRelocated
currentSolution.getRoute().get(relocationMove.getRoute()).getNodes().add(relocationMove.getPositionToBeInserted(), relocatedNode);
}
double newSolutionCost = 0;
for (int i = 0; i < currentSolution.getRoute().get(relocationMove.getRoute()).getNodes().size() - 1; i++) {
Node A = currentSolution.getRoute().get(relocationMove.getRoute()).getNodes().get(i);
Node B = currentSolution.getRoute().get(relocationMove.getRoute()).getNodes().get(i + 1);
newSolutionCost = newSolutionCost + distanceMatrix[A.getId()][B.getId()];
}
if (currentSolution.getRoute().get(relocationMove.getRoute()).getCost() + relocationMove.getMoveCost() != newSolutionCost) {
System.out.println("Something went wrong with the cost calculations !!!!");
}
// 更新 解的cost 和 路径的cost
currentSolution.setCost(currentSolution.getCost() + relocationMove.getMoveCost());
currentSolution.getRoute().get(relocationMove.getRoute()).setCost(currentSolution.getRoute().get(relocationMove.getRoute()).getCost() + relocationMove.getMoveCost());
setSolution(currentSolution);// 记录下答案,更新当前解
}
private void findBestRelocationMove(RelocationMove relocationMove,
Solution currentSolution,
double[][] distanceMatrix,
int numberOfVehicles) {
double bestMoveCost = Double.MAX_VALUE;
for (int j = 0; j < numberOfVehicles; j++) {
// on the Route j, find nodes A-->B-->C, F-->G
for (int relIndex = 1; relIndex < currentSolution.getRoute().get(j).getNodes().size() - 1; relIndex++) {
Node A = currentSolution.getRoute().get(j).getNodes().get(relIndex - 1);
Node B = currentSolution.getRoute().get(j).getNodes().get(relIndex);
Node C = currentSolution.getRoute().get(j).getNodes().get(relIndex + 1);
for (int afterInd = 0; afterInd < currentSolution.getRoute().get(j).getNodes().size() - 1; afterInd++) {
if (afterInd != relIndex && afterInd != relIndex - 1) {
Node F = currentSolution.getRoute().get(j).getNodes().get(afterInd);
Node G = currentSolution.getRoute().get(j).getNodes().get(afterInd + 1);
double costRemoved1 = distanceMatrix[A.getId()][B.getId()] + distanceMatrix[B.getId()][C.getId()];
double costRemoved2 = distanceMatrix[F.getId()][G.getId()];
double costRemoved = costRemoved1 + costRemoved2;
double costAdded1 = distanceMatrix[A.getId()][C.getId()];
double costAdded2 = distanceMatrix[F.getId()][B.getId()] + distanceMatrix[B.getId()][G.getId()];
double costAdded = costAdded1 + costAdded2;
double moveCost = costAdded - costRemoved;
if (moveCost < bestMoveCost) {
bestMoveCost = moveCost;
relocationMove.setPositionOfRelocated(relIndex);
relocationMove.setPositionToBeInserted(afterInd);
relocationMove.setMoveCost(moveCost);
relocationMove.setRoute(j);
}
}
}
}
}
setSolution(currentSolution);// 记录下答案 ?? why add this line? we never revise the info od solution in this func
}
}
第二种LocalSearch:
package com.vrp.app.solvers;
import com.vrp.app.Solver;
import com.vrp.app.VRP;
import com.vrp.app.components.Node;
import com.vrp.app.components.RelocationMove;
import com.vrp.app.components.Solution;
import java.util.ArrayList;
/**
* A local search is then designed for improving even further the initial solution generated at Component 3.
* This local search method considers all possible customer relocations (both intra- and inter-route).
* This means that at each iteration,
* the method explores all potential relocations of customers to any point of the existing solution.
*/
// 相比于intra-route Local search, 这种intra-route&Inter-route 表示既可以在一条路径内部做,也可以在路径之间做 relocation and insertion
public class LocalSearchIntraAndInterRelocation implements Solver {
private final int numberOfVehicles;
private final int numberOfCustomers;
private final ArrayList<Node> allNodes;
private final Node depot;
private final double[][] distanceMatrix;
private Solution solution;
public LocalSearchIntraAndInterRelocation(int numberOfVehicles, int numberOfCustomers, ArrayList<Node> allNodes, Node depot, double[][] distanceMatrix) {
this.numberOfVehicles = numberOfVehicles;
this.numberOfCustomers = numberOfCustomers;
this.allNodes = allNodes;
this.depot = depot;
this.distanceMatrix = distanceMatrix;
this.solution = new Solution();
}
@Override
public void run() {
boolean terminationCondition = false;
int localSearchIterator = 0;
// create a relocationMove and initialize it
RelocationMove relocationMove = new RelocationMove(-1, -1,
0, 0, Double.MAX_VALUE, Double.MAX_VALUE);
while (!terminationCondition) {
// find the best relocationMove and fill it into rolocationMove
findBestRelocationMove(relocationMove, solution, distanceMatrix, numberOfVehicles);
if (relocationMove.getMoveCost() < 0) {
applyRelocationMove(relocationMove, solution);
localSearchIterator = localSearchIterator + 1;
} else {
terminationCondition = true;
}
}
}
private void applyRelocationMove(RelocationMove relocationMove,
Solution currentSolution) {
Node relocatedNode = currentSolution.getRoute()
.get(relocationMove.getFromRoute()).getNodes()
.get(relocationMove.getPositionOfRelocated());
currentSolution.getRoute().get(relocationMove.getFromRoute())
.getNodes().remove(relocationMove.getPositionOfRelocated());
// 为什么”在同一路径上做后方删除、前方插入“,需要将原先的插入位置后移一位呢?
int whereToMove = (shouldShiftRelocation(relocationMove) ?
relocationMove.getPositionToBeInserted() + 1 : relocationMove.getPositionToBeInserted());
// 把 relocateNode插入到 路径to 位置whereToMove处
currentSolution.getRoute().get(relocationMove.getToRoute()).getNodes().add(whereToMove, relocatedNode);
// 更新当前解的cost, 路径to, 路径From 的 cost
currentSolution.setCost(currentSolution.getCost() + relocationMove.getMoveCost());
currentSolution.getRoute().get(relocationMove.getToRoute()).setCost(currentSolution.getRoute().get(relocationMove.getToRoute()).getCost() + relocationMove.getMoveCostTo());
currentSolution.getRoute().get(relocationMove.getFromRoute()).setCost(currentSolution.getRoute().get(relocationMove.getFromRoute()).getCost() + relocationMove.getMoveCostFrom());
// update the load info of Route 'from' and 'to'
if (relocationMove.getToRoute() != relocationMove.getFromRoute()) {// Route 'from' != Route 'to'
currentSolution.getRoute().get(relocationMove.getToRoute()).setLoad(relocationMove.getNewLoadTo());
currentSolution.getRoute().get(relocationMove.getFromRoute()).setLoad(relocationMove.getNewLoadFrom());
} else {
currentSolution.getRoute().get(relocationMove.getToRoute()).setLoad(relocationMove.getNewLoadTo());
}
setSolution(currentSolution);
}
boolean shouldShiftRelocation(RelocationMove relocationMove) {
// either insertPos is ahead of relocatePos or these two pos are not on the same route
return (relocationMove.getPositionToBeInserted() < relocationMove.getPositionOfRelocated())
|| relocationMove.getToRoute() != relocationMove.getFromRoute();
}
private void findBestRelocationMove(RelocationMove relocationMove,
Solution currentSolution,
double[][] distanceMatrix,
int numberOfVehicles) {
StringBuilder debug = new StringBuilder();
double bestMoveCost = Double.MAX_VALUE;
// 相比于intra-route Local search, 这种intra-route&Inter-route 表示既可以在一条路径内部做,也可以在路径之间做 relocation and insertion
for (int from = 0; from < numberOfVehicles; from++) {
for (int to = 0; to < numberOfVehicles; to++) {
// Route 'from' will lose Node 'B' bwt 'A' and 'C', while Route 'to' will obtain Node 'B' and insert it bwt 'F' and 'G"
// Node 0 and Node n-1 are depot, so skip them
for (int relFromIndex = 1; relFromIndex < currentSolution.getRoute().get(from).getNodes().size() - 1; relFromIndex++) {
Node A = currentSolution.getRoute().get(from).getNodes().get(relFromIndex - 1);
Node B = currentSolution.getRoute().get(from).getNodes().get(relFromIndex);
Node C = currentSolution.getRoute().get(from).getNodes().get(relFromIndex + 1);
for (int afterToInd = 0; afterToInd < currentSolution.getRoute().get(to).getNodes().size() - 1; afterToInd++) {
if ((afterToInd != relFromIndex && afterToInd != relFromIndex - 1) || from != to) {
// (同一\不同 路径上)不同位置发生relocation&insertion || 不同路径
Node F = currentSolution.getRoute().get(to).getNodes().get(afterToInd);
Node G = currentSolution.getRoute().get(to).getNodes().get(afterToInd + 1);
double costRemovedFrom = distanceMatrix[A.getId()][B.getId()] + distanceMatrix[B.getId()][C.getId()];
double costRemovedTo = distanceMatrix[F.getId()][G.getId()];
double costAddedFrom = distanceMatrix[A.getId()][C.getId()];
double costAddedTo = distanceMatrix[F.getId()][B.getId()] + distanceMatrix[B.getId()][G.getId()];
double moveCostFrom = costAddedFrom - costRemovedFrom; // how much cost this relocationMove brings up to Route 'From'
double moveCostTo = costAddedTo - costRemovedTo;// how much cost this relocationMove brings up to Route 'To'
double moveCost = moveCostFrom + moveCostTo;
if ((moveCost < bestMoveCost) &&
(from == to || // from!=to, 把from路径上的节点B移到to路径上时,依然要满足to路径的车容量约束
(currentSolution.getRoute().get(to).getLoad()
+ currentSolution.getRoute().get(from).getNodes().get(relFromIndex).getDemand()
<= currentSolution.getRoute().get(to).getCapacity())
)) {
bestMoveCost = moveCost;
relocationMove.setPositionOfRelocated(relFromIndex);
relocationMove.setPositionToBeInserted(afterToInd);
relocationMove.setMoveCostTo(moveCostTo);
relocationMove.setMoveCostFrom(moveCostFrom);
relocationMove.setFromRoute(from);
relocationMove.setToRoute(to);
relocationMove.setMoveCost(moveCost);
if (from != to) {// when we do relocation bwt different routes, their loads will be changed
relocationMove.setNewLoadFrom(currentSolution.getRoute().get(from).getLoad()
- currentSolution.getRoute().get(from).getNodes().get(relFromIndex).getDemand());
relocationMove.setNewLoadTo(currentSolution.getRoute().get(to).getLoad()
+ currentSolution.getRoute().get(from).getNodes().get(relFromIndex).getDemand());
} else {
relocationMove.setNewLoadFrom(currentSolution.getRoute().get(from).getLoad());
relocationMove.setNewLoadTo(currentSolution.getRoute().get(to).getLoad());
}
debug.append("From route: " + relocationMove.getFromRoute() + ", To Route: " + relocationMove.getToRoute() + ", New Load From:" + relocationMove.getNewLoadFrom() + ", New Load To:" + relocationMove.getNewLoadTo());
}
}
}
}
}
}
setSolution(currentSolution);
if (VRP.DEBUG_ROUTES) {
System.out.println(debug);
}
}
@Override
public void setSolution(Solution solution) {
this.solution = solution;
}
@Override
public Solution getSolution() {
return solution;
}
public int getNumberOfVehicles() {
return numberOfVehicles;
}
public int getNumberOfCustomers() {
return numberOfCustomers;
}
public ArrayList<Node> getAllNodes() {
return allNodes;
}
public Node getDepot() {
return depot;
}
public double[][] getDistanceMatrix() {
return distanceMatrix;
}
}
禁忌搜索算法
我暂时还没看懂这个代码……回头补充吧
禁忌搜索算法解VRPTW问题,但是这里代码的思路和这篇链接文章并不相同
2023年7月14日更新,我看懂了。这个禁忌搜索算法是在LocalSearchIntraAndInterRelocation的基础上加了禁忌表,每次找到新的relocation就判断一下“relocation所形成的新边是否处于被禁状态”,如果没有被禁并且新的relocation不会违反车的容量约束,那么 进一步考虑它是否会产生更好的目标函数值?并由此决定是否更新bestMoveCost和relocationMove
package com.vrp.app.solvers;
import com.vrp.app.Solver;
import com.vrp.app.VRP;
import com.vrp.app.components.Arc;
import com.vrp.app.components.Node;
import com.vrp.app.components.RelocationMove;
import com.vrp.app.components.Solution;
import java.util.ArrayList;
import java.util.Random;
/**
* A tabu-search method is finally implemented for
* improving the initial solution generated by Nearest Neighbor algoirthm.
* This tabu search method considers all possible customer relocations
* (both intra- and inter-route) with respect to the selected tabu policy.
* The method terminates after 200 iterations.
*/
public class TabuSearch implements Solver {
private final double TOLERANCE = 0.000001;
private Random globalRandom = new Random(1);
private final int numberOfVehicles;
private final int numberOfCustomers;
private final ArrayList<Node> allNodes;
private final double[][] distanceMatrix;
private Solution solution;
private int[][] tabuArcs;// 禁忌表,记录了各个边被允许解禁时的迭代次数
private int TABU;
public TabuSearch(int numberOfVehicles, int numberOfCustomers,
ArrayList<Node> allNodes, Node depot, double[][] distanceMatrix) {
this.numberOfVehicles = numberOfVehicles;
this.numberOfCustomers = numberOfCustomers;
this.allNodes = allNodes;
this.distanceMatrix = distanceMatrix;
this.solution = new Solution();
initTabuArcs();
}
private void initTabuArcs() {
tabuArcs = new int[allNodes.size()][allNodes.size()];
for (int i = 0; i < allNodes.size(); i++) {
Node from = allNodes.get(i);
for (int j = 0; j < allNodes.size(); j++) {
Node to = allNodes.get(j);
tabuArcs[from.getId()][to.getId()] = -1;
}
}
}
@Override
public void run() {
int tabuSearchIterator = 0;
int MAX_ITERATIONS = 100;
int bestSolutionIterator = 0;
StringBuilder debug = new StringBuilder();
TABU = 18;
Solution bestSolution = Solution.cloneSolution(solution);// 已构造出的初始可行解
RelocationMove relocationMove = new RelocationMove(-1, -1,
0, 0, Double.MAX_VALUE, Double.MAX_VALUE);
while (tabuSearchIterator < MAX_ITERATIONS) {
tabuSearchIterator = tabuSearchIterator + 1;
// 和前面的算法思路一样,先是找最好的relocation,更新到relocationMove中
// 传入当前迭代次数tabuSearch的目的是: 判断找到的最好候选relocation所涉及到的新弧 是否是还处在被禁状态
findBestRelocationMove(relocationMove, solution, distanceMatrix,
tabuSearchIterator, bestSolution, numberOfVehicles);
// 将最好的relocation应用到当前解上,更新当前解solution
applyRelocationMove(relocationMove, solution, tabuSearchIterator);
if (solution.getCost() < (bestSolution.getCost() - TOLERANCE)) {// found a better solu, then record it
bestSolution = Solution.cloneSolution(solution);
bestSolutionIterator = tabuSearchIterator;
}
debug.append("Iteration: " + tabuSearchIterator +
" Best Cost: " + bestSolution.getCost() +
" Current Cost: " + solution.getCost() + "\n");
if (tabuSearchIterator == 16 || tabuSearchIterator == 17) {
// 迭代到第16、17次时,打印出每辆车的路径,以及其他路径信息
debug.append("Iteration: " + tabuSearchIterator + "\n");
for (int j = 0; j < numberOfVehicles; j++) {
int vehicle = j + 1;
debug.append("New Assignment to Vehicle " + vehicle + ": ");
for (int k = 0; k < solution.getRoute().get(j).getNodes().size(); k++) {
debug.append(solution.getRoute().get(j).getNodes().get(k).getId() + " ");
}
debug.append("\n");
debug.append("Route Cost: " + solution.getRoute().get(j).getCost() + " - Route Load: " + solution.getRoute().get(j).getLoad() + "\n");
debug.append("\n");
}
}
}
if (VRP.DEBUG_ROUTES) {
debug.append("Best Solution Iteration: " + bestSolutionIterator + "\n");
System.out.println(debug);
}
}
private void findBestRelocationMove(RelocationMove relocationMove, Solution currentSolution, double[][] distanceMatrix, int iteration, Solution bestSol, int numberOfVehicles) {
Arc cr;// used to record those arcs waiting for a check about whether they are still forbidden
ArrayList<Arc> toBeCreated = new ArrayList<>(); // collect those new arcs involoved in this relocationMove
double bestMoveCost = Double.MAX_VALUE;
for (int from = 0; from < numberOfVehicles; from++) {
for (int to = 0; to < numberOfVehicles; to++) {
// Relocation: 从路径from中删除第relFromIndex个节点,将其插入到路径to中的afterToInd位置
for (int relFromIndex = 1; relFromIndex < currentSolution.getRoute().get(from).getNodes().size() - 1; relFromIndex++) {
Node A = currentSolution.getRoute().get(from).getNodes().get(relFromIndex - 1);
Node B = currentSolution.getRoute().get(from).getNodes().get(relFromIndex);
Node C = currentSolution.getRoute().get(from).getNodes().get(relFromIndex + 1);
for (int afterToInd = 0; afterToInd < currentSolution.getRoute().get(to).getNodes().size() - 1; afterToInd++) {
// (插入位置的两端点,即afterToInd和afterToInd+1, 不同于 删除位置)
if (afterToInd != relFromIndex && afterToInd != relFromIndex - 1) {
Node F = currentSolution.getRoute().get(to).getNodes().get(afterToInd);
Node G = currentSolution.getRoute().get(to).getNodes().get(afterToInd + 1);
double costRemovedFrom = distanceMatrix[A.getId()][B.getId()] + distanceMatrix[B.getId()][C.getId()];
double costRemovedTo = distanceMatrix[F.getId()][G.getId()];
double costAddedFrom = distanceMatrix[A.getId()][C.getId()];
double costAddedTo = distanceMatrix[F.getId()][B.getId()] + distanceMatrix[B.getId()][G.getId()];
double moveCostFrom = costAddedFrom - costRemovedFrom;
double moveCostTo = costAddedTo - costRemovedTo;
double moveCost = moveCostFrom + moveCostTo;
// previous: A-->B-->C, F--->G
// now : A-->C, F-->B-->G
toBeCreated.clear();// 准备收集这次relocation所涉及的新弧,后续判断他们是否被禁
cr = new Arc(A.getId(), C.getId());
toBeCreated.add(cr);
cr = new Arc(F.getId(), B.getId());
toBeCreated.add(cr);
cr = new Arc(B.getId(), G.getId());
toBeCreated.add(cr);
if (!isTabuArcs(toBeCreated, moveCost, currentSolution, iteration, bestSol)) {
// 如果这些新弧没有被禁,那么看这次relocation能否更优,即成本更小
if ((moveCost < bestMoveCost) && (from == to ||
// when `from!=to`, make sure that the addition of Node 'B' won't voliate the vehicle capacity constraint
(currentSolution.getRoute().get(to).getLoad()
+ currentSolution.getRoute().get(from).getNodes().get(relFromIndex).getDemand()
<= currentSolution.getRoute().get(to).getCapacity()))) {
bestMoveCost = moveCost;
relocationMove.setPositionOfRelocated(relFromIndex);
relocationMove.setPositionToBeInserted(afterToInd);
relocationMove.setMoveCostTo(moveCostTo);
relocationMove.setMoveCostFrom(moveCostFrom);
relocationMove.setFromRoute(from);
relocationMove.setToRoute(to);
relocationMove.setMoveCost(moveCost);
// update load
if (from != to) {
relocationMove.setNewLoadFrom(
currentSolution.getRoute().get(from).getLoad()
- currentSolution.getRoute().get(from).getNodes().get(relFromIndex).getDemand());
relocationMove.setNewLoadTo(
currentSolution.getRoute().get(to).getLoad()
+ currentSolution.getRoute().get(from).getNodes().get(relFromIndex).getDemand());
} else {
relocationMove.setNewLoadFrom(currentSolution.getRoute().get(from).getLoad());
relocationMove.setNewLoadTo(currentSolution.getRoute().get(to).getLoad());
}
}
}
}
}
}
}
}
}
private void applyRelocationMove(RelocationMove relocationMove, Solution currentSolution, int iterations) {
Node relocatedNode = currentSolution.getRoute().get(relocationMove.getFromRoute())
.getNodes().get(relocationMove.getPositionOfRelocated());
Node A = currentSolution.getRoute().get(relocationMove.getFromRoute()).getNodes().get(relocationMove.getPositionOfRelocated() - 1);
Node B = currentSolution.getRoute().get(relocationMove.getFromRoute()).getNodes().get(relocationMove.getPositionOfRelocated());
Node C = currentSolution.getRoute().get(relocationMove.getFromRoute()).getNodes().get(relocationMove.getPositionOfRelocated() + 1);
Node F = currentSolution.getRoute().get(relocationMove.getToRoute()).getNodes().get(relocationMove.getPositionToBeInserted());
Node G = currentSolution.getRoute().get(relocationMove.getToRoute()).getNodes().get(relocationMove.getPositionToBeInserted() + 1);
// 对于这个最好的relocationMove所涉及到的旧弧(用过的边),更新他们的“允许解禁时的迭代次数”(必然大于当前的迭代次数)
tabuArcs[A.getId()][B.getId()] = iterations + globalRandom.nextInt(TABU);
tabuArcs[B.getId()][C.getId()] = iterations + globalRandom.nextInt(TABU);
tabuArcs[F.getId()][G.getId()] = iterations + globalRandom.nextInt(TABU);
// delete Node B from routeFrom currentSolution.getRoute().get(relocationMove.getFromRoute()).getNodes().remove(relocationMove.getPositionOfRelocated());
// add Node B into routeTo
if ((relocationMove.getPositionToBeInserted() < relocationMove.getPositionOfRelocated()) || relocationMove.getToRoute() != relocationMove.getFromRoute()) {//!!
//?? why do we need to shift this insertPosition?
currentSolution.getRoute().get(relocationMove.getToRoute()).getNodes().add(relocationMove.getPositionToBeInserted() + 1, relocatedNode);
} else {
currentSolution.getRoute().get(relocationMove.getToRoute()).getNodes().add(relocationMove.getPositionToBeInserted(), relocatedNode);
}
// update the cost of current solution and RouteFrom
currentSolution.setCost(currentSolution.getCost() + relocationMove.getMoveCost());
currentSolution.getRoute().get(relocationMove.getFromRoute()).setCost(currentSolution.getRoute().get(relocationMove.getFromRoute()).getCost() + relocationMove.getMoveCostFrom());
// ??why not revise toRoute's cost??
// update the load of RouteTo and RouteFrom
if (relocationMove.getToRoute() != relocationMove.getFromRoute()) {
currentSolution.getRoute().get(relocationMove.getToRoute()).setLoad(relocationMove.getNewLoadTo());
currentSolution.getRoute().get(relocationMove.getFromRoute()).setLoad(relocationMove.getNewLoadFrom());
} else {
currentSolution.getRoute().get(relocationMove.getToRoute()).setLoad(relocationMove.getNewLoadTo());
}
setSolution(currentSolution);
}
private boolean isTabuArcs(ArrayList<Arc> toBeCrt, double moveCost, Solution currentSolution, int iteration, Solution bestSolution) {
// 如果当前relocationMove所带来的代价moveCost必然能够带来更好的解,那么不必考虑禁忌表,直接认为新弧没有被禁
if ((currentSolution.getCost() + moveCost) < (bestSolution.getCost() - TOLERANCE)) {
return false;// 新弧没有处于禁忌状态
}
for (int i = 0; i < toBeCrt.size(); i++) {
Arc arc = toBeCrt.get(i);
if (iteration < tabuArcs[arc.getN1()][arc.getN2()]) {// 当前的迭代次数iteration还不足以解禁它
return true;
}
}
return false;
}
@Override
public void setSolution(Solution solution) {
this.solution = solution;
}
@Override
public Solution getSolution() {
return solution;
}
}
这套代码的输入和输出
main function
// VRP.java
package com.vrp.app;
import com.vrp.app.components.Node;
import com.vrp.app.components.Solution;
import com.vrp.app.solvers.LocalSearchIntraAndInterRelocation;
import com.vrp.app.solvers.NearestNeighbor;
import com.vrp.app.solvers.LocalSearchIntraRelocation;
import com.vrp.app.solvers.TabuSearch;
import com.vrp.app.utils.Printer;
import java.util.ArrayList;
import java.util.Random;
public class VRP {
public static final boolean DEBUG_ROUTES = false;
private static int numberOfCustomers = 30;
private static int numberOfVehicles = 10;
private static int algorithm = 1;
private static final boolean PRINT = true;
public static void main(String[] args) {
Solution finalSolution;
String solverName = "";
setArgs(args); // obtain algorithm, numberOfCustomers, numberOfVehicles
ArrayList<Node> allNodes = initCustomers(numberOfCustomers);
Node depot = allNodes.get(0);
double[][] distanceMatrix = initDistanceMatrix(allNodes);
NearestNeighbor nearestNeighbor = new NearestNeighbor(numberOfCustomers, numberOfVehicles, depot, distanceMatrix, allNodes);
nearestNeighbor.run();
// 最近邻得到初始解,用下面的局部搜索、禁忌搜索改进这个初始解
switch (algorithm) {
case 1:
finalSolution = nearestNeighbor.getSolution();
solverName = "LocalSearch";
break;
case 2:
LocalSearchIntraRelocation localSearchIntraRelocation = new LocalSearchIntraRelocation(numberOfCustomers, numberOfVehicles, depot, distanceMatrix, allNodes);
localSearchIntraRelocation.setSolution(nearestNeighbor.getSolution());
localSearchIntraRelocation.run();
finalSolution = localSearchIntraRelocation.getSolution();
solverName = "LocalSearch with Intra Relocation";
break;
case 3:
LocalSearchIntraAndInterRelocation localSearchIntraAndInterRelocation
= new LocalSearchIntraAndInterRelocation(
numberOfVehicles, numberOfCustomers, allNodes, depot, distanceMatrix);
localSearchIntraAndInterRelocation.setSolution(nearestNeighbor.getSolution());
localSearchIntraAndInterRelocation.run();
finalSolution = localSearchIntraAndInterRelocation.getSolution();
solverName = "LocalSearch with Intra and Inner Relocation";
break;
case 4:
TabuSearch tabuSearch = new TabuSearch(numberOfVehicles, numberOfCustomers, allNodes, depot, distanceMatrix);
tabuSearch.setSolution(nearestNeighbor.getSolution());
tabuSearch.run();
finalSolution = tabuSearch.getSolution();
solverName = "\t TABU Search ";
break;
default:
throw new IllegalStateException("Unexpected value: " + algorithm);
}
if (PRINT) {
Printer.printResults(numberOfVehicles, finalSolution, solverName);
}
}
private static void setArgs(String[] args) {
if (args.length != 0) {
algorithm = Integer.parseInt(args[0]);
numberOfCustomers = Integer.parseInt(args[1]);
numberOfVehicles = Integer.parseInt(args[2]);
} else {// invalid input
System.out.println("Arguments should be 3: 1) Algorithm 2) Number of Customers and 3) Number of Vehicles");
System.exit(1);
}
}
private static double[][] initDistanceMatrix(ArrayList<Node> allNodes) {
double[][] distanceMatrix = new double[allNodes.size()][allNodes.size()];
for (int i = 0; i < allNodes.size(); i++) {
Node from = allNodes.get(i);
for (int j = 0; j < allNodes.size(); j++) {
Node to = allNodes.get(j);
double Delta_x = (from.getX() - to.getX());
double Delta_y = (from.getY() - to.getY());
double distance = Math.sqrt((Delta_x * Delta_x) + (Delta_y * Delta_y));
distance = Math.round(distance);
distanceMatrix[i][j] = distance;
}
}
return distanceMatrix;
}
private static ArrayList<Node> initCustomers(int numberOfCustomers) {
ArrayList<Node> allNodes = new ArrayList<>();// collect all the nodes
Node depot = new Node(50, 50, 0);
depot.setRouted(true);// depot已经被安排到路径里面了
allNodes.add(depot);
Random ran = new Random(150589);
for (int i = 1; i <= numberOfCustomers; i++) {
int x = ran.nextInt(100);
int y = ran.nextInt(100);
int demand = 4 + ran.nextInt(7);
Node customer = new Node(x, y, i, demand);
customer.setRouted(false);
allNodes.add(customer);
}
return allNodes;
}
}
Solution.java
package com.vrp.app.components;
import java.util.ArrayList;
public class Solution {
private double cost;
private ArrayList<Route> route;
public Solution() {
this.route = new ArrayList<>();
this.cost = 0;
}
@SuppressWarnings("unchecked")
public static Solution cloneSolution(Solution solution) {
Solution out = new Solution();
out.setCost(solution.getCost());
out.setRoute((ArrayList<Route>) solution.getRoute().clone());
return out;
}
public double getCost() {
return cost;
}
public void setCost(double costs) {
this.cost = costs;
}
public void setRoute(ArrayList<Route> route) {
this.route = route;
}
public ArrayList<Route> getRoute() {
return route;
}
}
Relocation.java
package com.vrp.app.components;
public class RelocationMove {
// 这里的relocationMove中的属性是 最近邻算法、局部搜索算法、禁忌搜索算法所涉及到的属性的并集,为这些算法共用
// 比如 fromRoute, toRoute, moveCostFrom, moveCostTo, newLoadFrom, newLoadTo 会被用在LocalSearchIntraAndInter算法中,而不会出现在 LocalSearchIntra算法中
// 'From', 'To' 分别代指两个路径
private int positionOfRelocated;
private int positionToBeInserted;
private int route;// to which route we apply this relocationMove
private double moveCost;
private int fromRoute;// from which route we choose to take away one node
private int toRoute; // to which route we choose to insert the node
private double moveCostTo;
private double moveCostFrom;
private int newLoadFrom;
private int newLoadTo;
public RelocationMove(int positionOfRelocated, int positionToBeInserted, int route, double moveCost) {
this.positionOfRelocated = positionOfRelocated;
this.positionToBeInserted = positionToBeInserted;
this.route = route;
this.moveCost = moveCost;
}
public RelocationMove(int positionOfRelocated, int positionToBeInserted, int fromRoute, int toRoute, double moveCostFrom, double moveCostTo) {
this.positionOfRelocated = positionOfRelocated;
this.positionToBeInserted = positionToBeInserted;
this.fromRoute = fromRoute;
this.toRoute = toRoute;
this.moveCostFrom = moveCostFrom;
this.moveCostTo = moveCostTo;
}
public int getPositionOfRelocated() {
return positionOfRelocated;
}
public int getPositionToBeInserted() {
return positionToBeInserted;
}
public int getRoute() {
return route;
}
public double getMoveCost() {
return moveCost;
}
public void setPositionOfRelocated(int positionOfRelocated) {
this.positionOfRelocated = positionOfRelocated;
}
public void setPositionToBeInserted(int positionToBeInserted) {
this.positionToBeInserted = positionToBeInserted;
}
public void setRoute(int route) {
this.route = route;
}
public void setMoveCost(double moveCost) {
this.moveCost = moveCost;
}
public int getFromRoute() {
return fromRoute;
}
public void setFromRoute(int fromRoute) {
this.fromRoute = fromRoute;
}
public int getToRoute() {
return toRoute;
}
public void setToRoute(int toRoute) {
this.toRoute = toRoute;
}
public double getMoveCostTo() {
return moveCostTo;
}
public void setMoveCostTo(double moveCostTo) {
this.moveCostTo = moveCostTo;
}
public double getMoveCostFrom() {
return moveCostFrom;
}
public void setMoveCostFrom(double moveCostFrom) {
this.moveCostFrom = moveCostFrom;
}
public int getNewLoadFrom() {
return newLoadFrom;
}
public void setNewLoadFrom(int newLoadFrom) {
this.newLoadFrom = newLoadFrom;
}
public int getNewLoadTo() {
return newLoadTo;
}
public void setNewLoadTo(int newLoadTo) {
this.newLoadTo = newLoadTo;
}
}
Printer.java
import com.vrp.app.components.Solution;
public class Printer {
public Printer(){
}
public static void printResults(int numberOfVehicles, Solution solution, String solverName) {
StringBuilder resultOutput = new StringBuilder();
resultOutput.append("* * * * * * * * * * * * * * * * * * * * * * * * * *\n");
resultOutput.append("\t VRP Starts ! \n");
resultOutput.append(" Solver Used : " + solverName + " \n");
resultOutput.append("* * * * * * * * * * * * * * * * * * * * * * * * * *\n");
for (int j = 0; j < numberOfVehicles; j++) {
int vehicle = j + 1;
resultOutput.append("Assignment to Vehicle " + vehicle + ": ");
for (int k = 0; k < solution.getRoute().get(j).getNodes().size(); k++) {
resultOutput.append(solution.getRoute().get(j).getNodes().get(k).getId() + " ");
}
resultOutput.append("\n");
resultOutput.append("Route Cost: " + solution.getRoute().get(j).getCost() + " - Route Load: " + solution.getRoute().get(j).getLoad() + "\n");
resultOutput.append("\n");
}
resultOutput.append("* * * * * * * * * * * * * * * * * * * * * * * * * *\n");
resultOutput.append("Total Cost: " + solution.getCost() + "\n");
System.out.println(resultOutput);
}
}
接口Solver
package com.vrp.app;
import com.vrp.app.components.Solution;
public interface Solver {
// 接口,都是抽象函数,后面最近邻算法、局部搜索算法、禁忌搜索算法会进一步实现这些算法
void run();
void setSolution(Solution solution);
Solution getSolution();
}
// Arc.java
package com.vrp.app.components;
public class Arc {
private int n1;// id of Node1
private int n2; // id of Node2
public Arc(int a, int b) {
this.n1 = a;
this.n2 = b;
}
public int getN1() {
return n1;
}
public void setN1(int n1) {
this.n1 = n1;
}
public int getN2() {
return n2;
}
public void setN2(int n2) {
this.n2 = n2;
}
}
Node.java
package com.vrp.app.components;
public class Node {
private int x;
private int y;
private int id;
private int demand;
boolean routable;//该节点/顾客 是否已经被插入到路径中
public Node(int x, int y, int id, int demand) {
this.x = x;
this.y = y;
this.id = id;
this.demand = demand;
}
public Node(int x, int y, int id) {
this.x = x;
this.y = y;
this.id = id;
}
public boolean getRouted() {
return routable;
}
public void setRouted(boolean rt) {
this.routable = rt;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getId() {
return id;
}
public void setId(int index) {
this.id = index;
}
public int getDemand() {
return demand;
}
}
Route.java
package com.vrp.app.components;
import java.util.ArrayList;
public class Route { // 一条路径对应一辆车
private ArrayList<Node> nodes; // 这辆车所经过的节点/顾客
private double cost; // distance
private int ID;
private int load;// 这辆车目前的负载
private int capacity;// 车的最大承重能力
public Route() {
this.cost = 0;
this.ID = -1;
this.capacity = 50;
this.load = 0;
this.nodes = new ArrayList<Node>();
}
public ArrayList<Node> getNodes() {
return nodes;
}
public double getCost() {
return cost;
}
public int getLoad() {
return load;
}
public int getID() {
return ID;
}
public int getCapacity() {
return capacity;
}
public void setCost(double cost) {
this.cost = cost;
}
public void setLoad(int load) {
this.load = load;
}
public void setID(int idx) {
this.ID = idx;
}
}