VRP启发式解法:局部搜索算法(relocation算子)、禁忌搜索算法(代码非原创)

参考文献:
微信公众号“运筹帷幄”:构造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;
    }
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值