分支定价算法求解VRPTW问题(代码非原创)

参考文献:微信公众号“程序猿声”关于分支定价求解VRPTW的代码
A tutorial on column generation and branch-and-price for vehicle routing problems

框架

对于VRPTW问题,先做线性松弛,调用列生成算法(一种解决大型线性规划问题的精确算法),得到最终RMP(Restricted master problem)。此时,如果得到的是整数解,那么结束算法;否则,更新上界、下界并分支,再在分支节点上重复上述步骤。

  • 列生成算法的具体步骤:对原松弛问题Min(主问题,MP),选择一些列,得到限制性主问题(RLMP)并求解它,得到对偶变量的值;用对偶变量的值,更新子问题的目标函数,目的是找到检验数为负的列,把新列添加给RLMP,继续求解,直到没有检验数为负的列生成了为止。
  • 分支定界算法的具体步骤:
    初始化上界、下界分别为系统最大值、系统最小值,下面我们希望通过分支来缩小他们之间的gap。
    对MIP做线性松弛,用单纯形法求解,如果解中满足Integer feasibility,那么结束算法;否则,更新下界并选择分支变量,做两个分支,对分支用单纯性算法求解,
    如果得到更好的整数解,更新上界;
    如果得到整数解但是没有更好,那么剪枝;
    如果得到的是非整数解,更新下界;
    如此循环,直到上界、下界之间的gap在可接受范围内为止。

细节

VRP问题的表述方式

分两种模型,一种是Arc-flow model,还有一种是set-covering model.
下面这种的是Arc-flow model
在这里插入图片描述
或者
在这里插入图片描述在这里插入图片描述

下面是set-covering model
翻译:上面的模型太弱了,无法使用分支定界算法。上面的模型可以通过Dantzig-Wolfe decomposition,得到下面的模型。下面的模型会有更好的线性松弛。
在这里插入图片描述

在这里插入图片描述
set-covering model的松弛解 可以转化为 arc-flow model的松弛解,但是 反过来不行。
在这里插入图片描述
这里列生成算法作用的是 set-covering model.

分支策略

分支策略添加的约束要和列生成算法相兼容。
基于路径的分支策略可能会导致整个BB树的不平衡,推荐使用基于弧段的分支策略,且后者更具代表性。
基于弧段的分支策略:
针对路径 r k r_k rk,选择一个弧段,当弧段的流量值为分数(介于0到1之间),进行分支:
分支1:解不能包含该弧段;
分支2:解必须包含该弧段;

  • 如何得到分支1?
    在主问题中,只要含有该弧段的都被删除掉;
    在子问题中,该弧段被删除,确保没有使用该弧段的列被生成;

  • 如何得到分支2?
    禁用那些”沾亲带故“的弧段,在代码中通过将弧段dist设置为无穷大做到这一点。

代码(Java调用Cplex)

  • 主函数 Main.java
package BranchAndPrice;

import java.io.IOException;
import java.util.ArrayList;

public class Main {

	public static void main(String[] args) throws IOException {
		branchandbound bp = new branchandbound(); // build bb tree
		paramsVRP instance = new paramsVRP();
		instance.initParams("D:\\VRP问题实践\\DengFahengBPVRPTW-master\\BPVRPTW-master\\dataset\\C101.TXT");
		ArrayList<route> initRoutes = new ArrayList<route>();
		ArrayList<route> bestRoutes = new ArrayList<route>();
		// create root node for bb tree
		bp.BBNode(instance, initRoutes, null, bestRoutes, 0); // fill bestRoutes
		double optCost = 0;
		System.out.println();
		System.out.println("solution >>>");
		for (route bestRoute : bestRoutes) {
			System.out.println(bestRoute.path);
			optCost += bestRoute.cost;
		}

		System.out.println("\nbest Cost = " + optCost);
	}

}

  • 分支定界算法
package BranchAndPrice;

import java.io.IOException;
import java.util.ArrayList;

public class branchandbound {
	double lowerbound;
	double upperbound;

	public branchandbound() {
		lowerbound = -1E10;
		upperbound = 1E10;
	}
	// try to update these two bound s.t. they are closer to each others

	class treeBB { // BB树上的节点
		// this is a linked tree list recording all the branching during Branch and Bound
		treeBB father; // link to the node processed before branching
		treeBB son0; // link to the son on the left of the tree (edge=0; first processed) => need it to compute the global lowerbound
		int branchFrom; // we branch on edges between cities => city origin of the edge
		int branchTo; // we branch on edges between cities => city destination of the edge
		int branchValue; // we branch on edges between cities => value of the branching (remove edge=0; set edge=1)
		double lowestValue; // lower bound on the solution if we start from this node (i.e. looking only down for this tree)
		boolean toplevel; // to compute the global lowerBound, need to know if everything above has been considered
	}

	// 对于给定分支节点branching,按照它的branchFrom和branchTo,重置dist[][],把某些边给禁了
	public void EdgesBasedOnBranching(paramsVRP userParam, treeBB branching,
	                                  boolean recur) {
		int i;
		if (branching.father != null) { // stop before root node
			if (branching.branchValue == 0) { // forbid this edge (in this direction) //  左分支禁用此弧
				// associate a very large distance to this edge to make it unattractive
				userParam.dist[branching.branchFrom][branching.branchTo] = userParam.verybig;
			} else {// 右分支 只允许 该弧被用, 禁用其他”沾亲带故“的弧
			    // impose this edge (in this direction)
				// associate a very large and unattractive distance to all edges
				// starting from "branchFrom" excepted the one leading to "branchTo"
				// and excepted when we start from depot (several vehicles)
				if (branching.branchFrom != 0) { // except 'branchTo'
					for (i = 0; i < branching.branchTo; i++)
						userParam.dist[branching.branchFrom][i] = userParam.verybig;
					// I guess, in this way, it can skip setting the distance between i and 'branchTo' this node
					for (i++; i < userParam.nbclients + 2; i++)
						userParam.dist[branching.branchFrom][i] = userParam.verybig;
				}
				// associate a very large and unattractive distance to all edges ending
				// at "branchTo" excepted the one starting from "branchFrom"
				// and excepted when the destination is the depot (several vehicles)
				if (branching.branchTo != userParam.nbclients + 1) {
					for (i = 0; i < branching.branchFrom; i++)
						userParam.dist[i][branching.branchTo] = userParam.verybig;
					for (i++; i < userParam.nbclients + 2; i++)
						userParam.dist[i][branching.branchTo] = userParam.verybig;
				}
				// forbid the edge in the opposite direction
				userParam.dist[branching.branchTo][branching.branchFrom] = userParam.verybig;
			}
			if (recur) EdgesBasedOnBranching(userParam, branching.father, true);// ??
		}
	}

	public boolean BBNode(paramsVRP userParam, ArrayList<route> routes,
                          treeBB branching, ArrayList<route> bestRoutes, int depth)
			throws IOException {// bestRoutes用于记录目前收集到的最好的可行路径
		// userParam (input) : all the parameters provided by the users (cities,
		// roads...)
		// routes (input) : all (but we could decide to keep only a subset) the
		// routes considered up to now (to initialize the Column generation process)
		// branching (input): BB branching context information for the current node
		// to process (branching edge var, branching value, branching from...)
		// bestRoutes (output): best solution encountered

		// routes 收集了 很多路径,那些.getQ>0的路径表示 被使用了
		int i, j, bestEdge1, bestEdge2, prevcity, city, bestVal;// previous city
		double coef, bestObj, change, CGobj;
		boolean feasible;

		try {

			// check first that we need to solve this node. Not the case if we have
			// already found a solution within the gap precision
			if ((upperbound - lowerbound) / upperbound < userParam.gap)
				return true;

			// init
			if (branching == null) { // root node - first call
				// first call - root node
				treeBB newNode = new treeBB();
				newNode.father = null;
				newNode.toplevel = true; // ??
				newNode.branchFrom = -1;
				newNode.branchTo = -1;
				newNode.branchValue = -1;
				newNode.son0 = null;
				branching = newNode;
			}

			// display some local info
			if (branching.branchValue < 1) {// 这条弧段 被禁用
				System.out.println("\nEdge from " + branching.branchFrom + " to "
						+ branching.branchTo + ": forbid");
			} else {// 这条弧段 被选择
				System.out.println("\nEdge from " + branching.branchFrom + " to "
						+ branching.branchTo + ": set");
			}
			int MB = 1024 * 1024;
			Runtime runtime = Runtime.getRuntime();
			System.out.print("Java Memory=> Total:" + (runtime.totalMemory() / MB)
					+ " Max:" + (runtime.maxMemory() / MB) + " Used:"
					+ ((runtime.totalMemory() - runtime.freeMemory()) / MB) + " Free: "
					+ runtime.freeMemory() / MB);

			// Compute a solution for this node using Column generation
			columngen CG = new columngen();

			CGobj = CG.computeColGen(userParam, routes);// objValue of RLMP, relaxed linear master problem
			// 生成的新列都会加入到 route中
			// feasible ? Does a solution exist?
			if ((CGobj > 2 * userParam.maxlength) || (CGobj < -1e-6)) {
				// can only be true when the routes in the solution include forbidden edges (can happen when the BB set branching values)
				System.out.println("RELAX INFEASIBLE | Lower bound: " + lowerbound
						+ " | Upper bound: " + upperbound + " | Gap: "
						+ ((upperbound - lowerbound) / upperbound) + " | BB Depth: "
						+ depth + " | " + routes.size() + " routes");
				return true; // stop this branch
			}
			branching.lowestValue = CGobj;// 当前分支的下界, 当前分支节点处的最终的RMP的最优值
			// routes 是 最终的RLMP的最优基 对应的路径

			// update the global lowerBound when required
			if ((branching.father != null) && (branching.father.son0 != null)
					&& branching.father.toplevel) {// 有父节点,且父节点有左孩子,父节点的lowerbound已经计算过了
				// all nodes above and on the left have been processed=> we can compute
				// a new lowerBound
				lowerbound = Math.min(branching.lowestValue, branching.father.son0.lowestValue);
				branching.toplevel = true;
			} else if (branching.father == null) {
				// root node
				lowerbound = CGobj;
			}

			if (branching.lowestValue > upperbound) {
				CG = null;
				System.out.println("CUT | Lower bound: " + lowerbound
						+ " | Upper bound: " + upperbound + " | Gap: "
						+ ((upperbound - lowerbound) / upperbound) + " | BB Depth: "
						+ depth + " | Local CG cost: " + CGobj + " | " + routes.size()
						+ " routes");
				return true; // cut this useless branch
			} else {
				// ///
				// check the (integer) feasibility. Otherwise search for a branching
				// variable
				feasible = true;
				bestEdge1 = -1;
				bestEdge2 = -1;
				bestObj = -1.0;
				bestVal = 0;
				// 基于路段的分支策略,书上的第二种
				// 路径的使用次数 转成 边的使用次数,切换了 决策变量
				// transform the path variable (of the CG model) into edges variables
				for (i = 0; i < userParam.nbclients + 2; i++) {
					java.util.Arrays.fill(userParam.edges[i], 0.0); // initialize the ith row
					// userParam.egdes is an array
				}
				for (route r : routes) {
					if (r.getQ() > 1e-6) {// 使用路径r的次数 大于 0,但是因为CG解的是线性松弛问题,所以r.getQ是double型
						// we consider only the routes in the current local solution
						ArrayList<Integer> path = r.getpath(); // get back the sequence of
						// cities (path for this route)
						prevcity = 0;
						for (i = 1; i < path.size(); i++) {
							city = path.get(i);
							// 把 路径上的Q值 赋予到 路径的边上
							userParam.edges[prevcity][city] += r.getQ(); // convert into edges
							prevcity = city;
						}
					}
				}

				// find a fractional edge 选择分支弧段
				for (i = 0; i < userParam.nbclients + 2; i++) {
					for (j = 0; j < userParam.nbclients + 2; j++) {
						coef = userParam.edges[i][j];
						if ((coef > 1e-6) && ((coef < 0.9999999999) || (coef > 1.0000000001))) {
							// ?? what if coef=2, 这种情况也同样被标记成feasible=false??有可能出现么? 答:不会,值介于 0到1之间
							// coef 大于0, 且, 小于1或大于1
							// coef is fractional// 介于 0到1之间
							// this route has a fractional coefficient in the solution =>
							// should we branch on this one?
							feasible = false;
							// what if we impose this route in the solution? Q=1
							// keep the ref of the edge which should lead to the largest change
							change = Math.min(coef, Math.abs(1.0 - coef));// min(0.2,0.8) = 0.2
							change *= routes.get(i).getcost();
							if (change > bestObj) { // ??
								bestEdge1 = i;
								bestEdge2 = j;
								bestObj = change;
								bestVal = (Math.abs(1.0 - coef) > coef) ? 0 : 1;// bestVal=1 if coef>0.5
							} // 选择分支弧段 (i_q,i_(q+1))
						}
					}
				}
				// 如果需要分支,选定 分支弧段为 [bestEdge1,bestEdge2]

				if (feasible) { // 说明 没有 取值为分数的 edge
					if (branching.lowestValue < upperbound) { // new incumbant feasible solution!
						upperbound = branching.lowestValue;
						bestRoutes.clear();
						for (route r : routes) {
							if (r.getQ() > 1e-6) {// 投入使用的路径,记录到bestRoutes中
								// 创建复制品,存到bestRoutes中
								route optim = new route();
								optim.setcost(r.getcost());
								optim.path = r.getpath();
								optim.setQ(r.getQ());
								bestRoutes.add(optim);
							}
						}
						System.out.println("OPT | Lower bound: " + lowerbound
								+ " | Upper bound: " + upperbound + " | Gap: "
								+ ((upperbound - lowerbound) / upperbound) + " | BB Depth: "
								+ depth + " | Local CG cost: " + CGobj + " | " + routes.size()
								+ " routes");
						System.out.flush();
					} else { //?? feasible && branching.lowerestValue>=upperbound
						// since we already got '>' done, so here, (feasible && branching.lowerestValue=upperbound)
                       // 是整数解,但不是比upperbound更好的整数解,所以 无需记录
                        System.out.println("FEAS | Lower bound: " + lowerbound
                                + " | Upper bound: " + upperbound + " | Gap: "
                                + ((upperbound - lowerbound) / upperbound) + " | BB Depth: "
                                + depth + " | Local CG cost: " + CGobj + " | " + routes.size()
                                + " routes");
                    }
					return true;
				} else {// infeasible 、、 fraction
					System.out.println("INTEG INFEAS | Lower bound: " + lowerbound
							+ " | Upper bound: " + upperbound + " | Gap: "
							+ ((upperbound - lowerbound) / upperbound) + " | BB Depth: "
							+ depth + " | Local CG cost: " + CGobj + " | " + routes.size()
							+ " routes");
					System.out.flush();
					// ///
					// branching (diving strategy)

					// 分支1: 删除掉含有弧段[bestEdge1,bestEdge2]的列/路径
					// first branch -> set edges[bestEdge1][bestEdge2]=0
					// record the branching information in a tree list
					treeBB newNode1 = new treeBB();
					newNode1.father = branching;
					newNode1.branchFrom = bestEdge1;
					newNode1.branchTo = bestEdge2;
					newNode1.branchValue = bestVal; // ?!first version was not with bestVal
					// but with 0
					newNode1.lowestValue = -1E10;
					newNode1.son0 = null;

					// branching on edges[bestEdge1][bestEdge2]=0
					//在函数EdgesBasedOnBraning函数中, newNode1.branchValue 提示此分支是被禁用的
					EdgesBasedOnBranching(userParam, newNode1, false); // 修改了dist[][]
					// ?? 为什么recur取值为false??

					// the initial lp for the CG contains all the routes of the previous
					// solution less(去掉分支的边) the routes containing this arc (arc bestEdge1-->bestEdge2)
					// 在routes的基础上,删除掉含有弧段[bestEdge1,bestEdge2]的列/路径;记录余下路径到nodeRoutes中
					ArrayList<route> nodeRoutes = new ArrayList<route>();
					for (route r : routes) {
						ArrayList<Integer> path = r.getpath();
						boolean accept = true;
						if (path.size() > 3) { // we must keep trivial routes
							// Depot-City-Depot in the set to ensure
							// feasibility of the CG
							prevcity = 0;
							for (j = 1; accept && (j < path.size()); j++) {
								city = path.get(j);
								if ((prevcity == bestEdge1) && (city == bestEdge2))
									accept = false;
								prevcity = city;
							}
						}
						if (accept) nodeRoutes.add(r);
					}

					boolean ok;
					ok = BBNode(userParam, nodeRoutes, newNode1, bestRoutes, depth + 1);
					nodeRoutes = null; // free memory
					if (!ok) {// “没结果”会返回false, "有结果"指 因最优性或可行性被剪枝、找到最优解等。
						return false;
					}

					branching.son0 = newNode1;

					// 分支2:必须“直接”含有弧段[bestEdge1,bestEdge2]的路径,排除掉 “沾亲带故”的路径
					// second branch -> set edges[bestEdge1][bestEdge2]=1
					// record the branching information in a tree list
					treeBB newNode2 = new treeBB();
					newNode2.father = branching;
					newNode2.branchFrom = bestEdge1;
					newNode2.branchTo = bestEdge2;
					newNode2.branchValue = 1 - bestVal; // first version: always 1
					newNode2.lowestValue = -1E10;
					newNode2.son0 = null;

					// branching on edges[bestEdge1][bestEdge2]=1
					// second branching=>need to reinitialize the dist matrix
					// why, Ans: 因为branch newNode1之后,调用了EdgeBasedOnBranching函数,修改到了dist[][]
					for (i = 0; i < userParam.nbclients + 2; i++) {
                        System.arraycopy(userParam.distBase[i], 0, userParam.dist[i], 0,
                              userParam.nbclients + 2);
                    }// 从这里看到distBase的作用在于,为 dist矩阵 存根
					//reinitialize了,因此需要recur递归一下
					EdgesBasedOnBranching(userParam, newNode2, true); // 为什么这里recur取值true?
					// the initial lp for the CG contains all the routes of the previous
					// solution less the routes incompatible with this arc,这些路径的弧段都被EdgeBasedOnBranching函数标记为verybig
					// “沾亲带故”: 那些路径要么含节点bestEdge1,但不是直接去向bestEdge2; 要么含节点bestEdge2,但不是直接从bestEdge1来
					// 从routes中 选 含有此弧段的路径, 记录在 nodeRoute2中
					ArrayList<route> nodeRoutes2 = new ArrayList<route>();
					for (route r : routes) {
						ArrayList<Integer> path = r.getpath();
						boolean accept = true;
						if (path.size() > 3) { // we must keep trivial routes
							// Depot-City-Depot in the set to ensure
							// feasibility of the CG
							prevcity = 0;
							for (i = 1; accept && (i < path.size()); i++) {
								city = path.get(i);
								if (userParam.dist[prevcity][city] >= userParam.verybig - 1E-6) accept = false;
								prevcity = city;
							}
						}
						if (accept) nodeRoutes2.add(r);
					}

					ok = BBNode(userParam, nodeRoutes2, newNode2, bestRoutes, depth + 1);
					nodeRoutes2 = null;

					// update lowest feasible value of this node
					branching.lowestValue = Math.min(newNode1.lowestValue, newNode2.lowestValue);

					return ok;// boolean
				}
			}

		} catch (IOException e) {
			System.err.println("Error: " + e);
		}
		return false;
	}

}

  • 列生成算法 columngen.java
package BranchAndPrice;

/**
 * Asymetric VRP with Resources Constraints (Time Windows and Capacity)
 * Branch and Price algorithm (Branch and Bound + Column generation)
 * For educational purpose only!  No code optimization.  Just to understand the main basic steps of the B&P algorithm.
 * Pricing through Dynamic Programming of the Short Path Problem with Resources Constraints (SPPRC)
 * Algorithm inspired by the book
 * Desrosiers, Desaulniers, Solomon, "Column Generation", Springer, 2005 (GERAD, 25th anniversary)
 * => Branch and bound (class BPACVRPTW)
 * => Column generation (class columngen) : chapter 3
 * => Pricing SPPRC (class SPPRC): chapter 2
 * CPLEX code for the column generation inspired by the example "CutStock.java" provided in the examples directory of the IBM ILOG CPLEX distribution
 *
 * @author mschyns
 * M.Schyns@ulg.ac.be
 */

import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;

import ilog.concert.*;
import ilog.cplex.*;

public class columngen {

	static class IloNumVarArray {
		// Creation of a new class similar to an ArrayList for CPLEX unknowns
		// taken from "cutsotck.java"
		int _num = 0;
		IloNumVar[] _array = new IloNumVar[32];

		void add(IloNumVar ivar) {// automative expansion
			if (_num >= _array.length) {
				IloNumVar[] array = new IloNumVar[2 * _array.length];
				System.arraycopy(_array, 0, array, 0, _num);
				_array = array;
			}
			_array[_num++] = ivar;
		}

		IloNumVar getElement(int i) {
			return _array[i];
		}

		int getSize() {
			return _num;
		}
	}

	public double computeColGen(paramsVRP userParam, ArrayList<route> routes)
			throws IOException {// 生成的新列都会加入到 routes中
		int i, j, prevcity, city;
		double cost, obj;
		double[] pi; // p_i, the value of dual variables
		boolean oncemore;

		try {

			// ---------------------------------------------------------
			// construct the model for the Restricted Master Problem
			// ---------------------------------------------------------
			// warning: for clarity, we create a new cplex env each time we start a
			// Column Generation
			// this class contains (nearly) everything about CG and could be used
			// independently
			// However, since the final goal is to encompass it inside 锟� Branch and
			// Bound (BB),
			// it would (probably) be better to create only once the CPlex env when we
			// initiate the BB and to work with the same (but adjusted) lp matrix each
			// time
			IloCplex cplex = new IloCplex();

			IloObjective objfunc = cplex.addMinimize();

			// for each vertex/client, one constraint (chapter 3, 3.23 )
			IloRange[] lpmatrix = new IloRange[userParam.nbclients];// start from 0
			for (i = 0; i < userParam.nbclients; i++)
				lpmatrix[i] = cplex.addRange(1.0, Double.MAX_VALUE); // 表示每个客户都要被服务到
			// for each constraint, right member >=1
			// lpmatrix[i] = cplex.addRange(1.0, 1.0);
			// or for each constraint, right member=1 ... what is the best?

			// Declaration of the variables
			IloNumVarArray y = new IloNumVarArray(); // y_p to define whether a path p
			// is used
			// y_p 表示这条路是否使用
			// 集覆盖模型

			// Populate the lp matrix and the objective function
			// first with the routes provided by the argument 'routes' of the function
			// (in the context of the Branch and Bound, it would be a pity to start
			// again the CG from scratch at each node of the BB!)
			// (we should reuse parts of the previous solution(s))
			// 复用之前的路径们,建立他们对应的列
			for (route r : routes) {
				int v;
				cost = 0.0;
				prevcity = 0;
				// total cost of route r
				for (i = 1; i < r.getpath().size(); i++) {
					city = r.getpath().get(i);
					cost += userParam.dist[prevcity][city];
					prevcity = city;
				}

				r.setcost(cost);
				// 上面计算新路径的成本c_p
				// 按列建模,为RLMP添加新的列
				IloColumn column = cplex.column(objfunc, r.getcost());// obj coefficient
				// constraint coefficient
				for (i = 1; i < r.getpath().size() - 1; i++) { // nodes this route passed, except two depots
					v = r.getpath().get(i) - 1;// the ith customer on the route r
					column = column.and(cplex.column(lpmatrix[v], 1.0));
					// coefficient of y_i in (3.23) => 0 for the other y_p
				}// say 0-2-3-6-0, 这一列第1,2,5行处是1,其余都是0
				// creation of the variable y_i whose range is [0,+++]
				y.add(cplex.numVar(column, 0.0, Double.MAX_VALUE));

			}
			// 新建一些列
			// complete the lp with basic route to ensure feasibility
			// initially choose several columns, 初始基可行解,每条路径只访问一个客户,构造得RLMP,并将此路径添加到routes
			if (routes.size() < userParam.nbclients) { // a priori true only the first time
				for (i = 0; i < userParam.nbclients; i++) { // customer i
					cost = userParam.dist[0][i + 1]
							+ userParam.dist[i + 1][userParam.nbclients + 1];
					// 新列加到目标函数上
					IloColumn column = cplex.column(objfunc, cost); // obj coefficient
					// 新列加到约束上
					column = column.and(cplex.column(lpmatrix[i], 1.0)); // coefficient of
					// y_i in (3.23)
					// => 0 for the
					// other y_p
					// 新列产生的新变量
					y.add(cplex.numVar(column, 0.0, Double.MAX_VALUE)); // creation of the
					// variable y_i
					// 新列对应的 新路径
					route newroute = new route();
					newroute.addcity(0);
					newroute.addcity(i + 1);
					newroute.addcity(userParam.nbclients + 1);
					newroute.setcost(cost);
					routes.add(newroute);
				}
			}

			// cplex.exportModel("model.lp");

			// CPlex params
			cplex.setParam(IloCplex.IntParam.RootAlgorithm, IloCplex.Algorithm.Primal);
			cplex.setOut(null);
			// cplex.setParam(IloCplex.DoubleParam.TiLim,30); // max number of
			// seconds: 2h=7200 24h=86400

			// ---------------------------------------------------------
			// column generation process
			// ---------------------------------------------------------
			DecimalFormat df = new DecimalFormat("#0000.00"); // 保留两位小数的十进制格式
			oncemore = true;
			double[] prevobj = new double[100]; // previous objective values, used to check stability
			int nbroute; // number of routes
			int previ = -1; // previous index
			while (oncemore) { // oncemore 表示 是否找到了新列,是那么继续找,否那么结束列生成

				oncemore = false;// 后面如果找到了新列,就会被更新成true
				// ---------------------------------------s------------------
				// solve the current RMP
				// ---------------------------------------------------------
				if (!cplex.solve()) {
					System.out.println("CG: relaxation infeasible!");
					return 1E10;
				}// now, CG: relaxation is feasible
				prevobj[(++previ) % 100] = cplex.getObjValue();
				// store the 30 last obj values to check stability afterwards

				// System.out.println(cplex.getStatus());
				// cplex.exportModel("model.lp");

				// ---------------------------------------------------------
				// solve the subproblem to find new columns (if any)
				// ---------------------------------------------------------
				// 利用对偶变量的值, 更新子问题的目标函数
				// first define the new costs for the subproblem objective function
				// (SPPRC)
				pi = cplex.getDuals(lpmatrix); // dual variables of those constraints 'lpmatrix'
				for (i = 1; i < userParam.nbclients + 1; i++) // from customer i
					for (j = 0; j < userParam.nbclients + 2; j++) // to any sites
						userParam.cost[i][j] = userParam.dist[i][j] - pi[i - 1];
				// 修改到 userParams.cost[][], 后面解子问题SPPRC会使用到这一结果
				// 详细见,SPPRC.java的结尾部分,扩展路径得到的新标签的cost的计算
				// 因为已经在paramVRP.initParam设置过不可行边的距离为 正无穷, 所以 这里第二个for循环是0~nbclients+2

				// start dynamic programming
				SPPRC sp = new SPPRC();// subproblem
				ArrayList<route> routesSPPRC = new ArrayList<route>();

				nbroute = userParam.nbclients; // arbitrarily limit to the 5 first
				// shortest paths with negative cost
				// if ((previ>100) &&
				// (prevobj[(previ-3)%100]-prevobj[previ%100]<0.0003*Math.abs((prevobj[(previ-99)%100]-prevobj[previ%100]))))
				// {
				// System.out.print("/");
				// complete=true; // it the convergence is too slow, start a "complete"
				// shortestpast
				// }
				sp.shortestPath(userParam, routesSPPRC, nbroute); // fill routesSPPRC, 收集到 检验数为负 的可行最短路径
				sp = null;

				// /
				// parameter here
				// 根据子问题求解结果,添加新列、新决策变量
				if (routesSPPRC.size() > 0) {
					for (route r : routesSPPRC) {
//             if (userParam.debug) {
//             System.out.println(" "+r.getcost());
//             }
						ArrayList<Integer> rout = r.getpath();
						prevcity = rout.get(1);
						cost = userParam.dist[0][prevcity];
						IloColumn column = cplex.column(lpmatrix[rout.get(1) - 1], 1.0);
						for (i = 2; i < rout.size() - 1; i++) {
							city = rout.get(i);
							cost += userParam.dist[prevcity][city];
							prevcity = city;
							column = column.and(cplex.column(lpmatrix[rout.get(i) - 1], 1.0));
							// coefficient of y_i in (3.23) => 0 for the other y_p
						}
						cost += userParam.dist[prevcity][userParam.nbclients + 1];
						// obj func
						column = column.and(cplex.column(objfunc, cost));
						// decision variable
						y.add(cplex.numVar(column, 0.0, Double.MAX_VALUE, // 根据新建列,创建一个变量,加入模型中
								"P" + routes.size())); // creation of the variable y_i
						// new route
						r.setcost(cost);
						routes.add(r);

						oncemore = true;// 已经找到了新路径,提示下一轮接着找
					}
					System.out.print("\nCG Iter " + previ + " Current cost: "
							+ df.format(prevobj[previ % 100]) + " " + routes.size()
							+ " routes"); // routes.size 表示 当前已经有多少条路径了
					System.out.flush();// ??
				}
				//if (previ % 50 == 0)
				routesSPPRC = null;
			}// end while(oncemore)
			// finished finding and adding new columns

			System.out.println();

			// final result fo cplex
			for (i = 0; i < y.getSize(); i++) // variables
				// route's Q value means whether this route is chosed or not
				routes.get(i).setQ(cplex.getValue(y.getElement(i)));// set Q value of route i as y_i
			obj = cplex.getObjValue(); // mmmmhhh: to check. To be entirely safe, we
			// should recompute the obj using the distBase
			// matrix instead of the dist matrix
			// ?? I thought disBase and dist matrix are the same....

			cplex.end();
			return obj;
		} catch (IloException e) {
			System.err.println("Concert exception caught '" + e + "' caught");
		}
		return 1E10;
	}
}

  • 用标号法求解带资源约束的最短路径问题
package BranchAndPrice;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.TreeSet;

// shortest path with resource constraints
// inspired by Irnish and Desaulniers, "SHORTEST PATH PROBLEMS WITH RESOURCE CONSTRAINTS"
// for educational demonstration only - (nearly) no code optimization
//
// four main lists will be used:
// labels: array (ArrayList) => one dimensional unbounded vector
//		 list of all labels created along the feasible paths (i.e. paths satisfying the resource constraints)
//		
// U: sorted list (TreeSet) => one dimensional unbounded vector
//		sorted list containing the indices of the unprocessed labels (paths that can be extended to obtain a longer feasible path)
//
// P: sorted list (TreeSet) => one dimensional unbounded vector
//		sorted list containing the indices of the processed labels ending at the depot with a negative cost
//
// city2labels: matrix (array of ArrayList) => nbClients x unbounded
//		for each city, the list of (indices of the) labels attached to this city/vertex
//		before processing a label at vertex i, we compare pairwise all labels at the same vertex to remove the dominated ones

public class SPPRC {
	paramsVRP userParam;// #vehicles, capacity, ready time, due time,...
	ArrayList<label> labels; // list of labels

	class label { //路径和资源消耗情况,组成标签
		// we use a labelling algorithm. 标签算法
		// labels are attached to each vertex to specify the state of the resources 标签标识了各种资源的使用状态
		// when we follow a corresponding feasible path ending at this vertex 沿可行路径到达当前顶点
		public int city;                // current vertex
		public int indexPrevLabel;    // previous label in the same path (i.e. previous vertex in the same path with the state of the resources)
		// cost,tTime,demand表示这个标签的资源消耗情况
		public double cost;                // first resource: cost (e.g. distance or strict travel time)
		public float tTime;                // second resource: travel time along the path (including wait time and service time)
		public double demand;                // third resource: demand,i.e. total quantity delivered to the clients encountered on this path
		public boolean dominated;            // is this label dominated by another one? i.e. if dominated, forget this path.
		public boolean[] vertexVisited;

		label(int a1, int a2, double a3, float a4, double a5, boolean a6, boolean[] a7) {
			city = a1;
			indexPrevLabel = a2;
			cost = a3;
			tTime = a4;
			demand = a5;
			dominated = a6;
			vertexVisited = a7;
		}
	} // end class label

	class MyLabelComparator implements Comparator<Integer> {
		// the U treeSet is an ordered list
		// to maintain the order, we need to define a comparator: cost is the main criterium
		public int compare(Integer a, Integer b) {
			label A = labels.get(a);
			label B = labels.get(b);

			// Be careful!  When the comparator returns 0, it means that the two labels are considered EXACTLY the same ones!
			// This comparator is not only used to sort the lists!  When adding to the list, a value of 0 => not added!!!!!
			// 因为这里的cost等都是double型,在计算机中存在精度的问题,所以需要自己定义
			// 先比较cost, --> city when cities are the same, 继续比较 time,demand, 访问节点顺序
			if (A.cost - B.cost < -1e-7)
				return -1;
			else if (A.cost - B.cost > 1e-7)
				return 1;
			else {
				if (A.city == B.city) {
					if (A.tTime - B.tTime < -1e-7)
						return -1;
					else if (A.tTime - B.tTime > 1e-7)
						return 1;
					else if (A.demand - B.demand < -1e-7)
						return -1;
					else if (A.demand - B.demand > 1e-7)
						return 1;
					else {
						int i = 0;
						while (i < userParam.nbclients + 2) {
							if (A.vertexVisited[i] != B.vertexVisited[i]) {
								if (A.vertexVisited[i])// 相比于B, A提前访问了i,所以返回A
									return -1;
								else
									return 1;
							}
							i++;
						}
						return 0;
					}
				} else if (A.city > B.city)
					return 1;
				else
					return -1;
			}
		}
	}


	public void shortestPath(paramsVRP userParamArg, ArrayList<route> routes, int nbRoute) {
		// 标号算法的主体部分,因为有删除标签的行为,所以标签校正算法
		label current;
		int i, j, idx, nbsol, maxSol;
		double d, d2;//cumutive demand of the next customer and the next and next customer
		int[] checkDom;
		float tt, tt2; // timePoint when we arrive the next customer i and the next and next customer j
		Integer currentidx; // the index of current label

		this.userParam = userParamArg;
		// unprocessed labels list => ordered TreeSet List (?optimal:  need to be sorted like this?)
		// 这里的“未处理”表示“未拓展”的
		TreeSet<Integer> U = new TreeSet<Integer>(new MyLabelComparator());   // unprocessed labels list

		// processed labels list => ordered TreeSet List , “处理过的”表示“已经拓展的”
		TreeSet<Integer> P = new TreeSet<Integer>(new MyLabelComparator());   // processed labels list

		// array of labels // 一个标签,很像一个状态:从哪里来,现在在哪里,路上访问了谁,耗费了多少资源
		labels = new ArrayList<label>(2 * userParam.nbclients); // initial size at least larger than nb clients
		boolean[] cust = new boolean[userParam.nbclients + 2];

		// for depot 0
		cust[0] = true;// vertexVisited
		for (i = 1; i < userParam.nbclients + 2; i++) cust[i] = false;
		labels.add(new label(0, -1, 0.0, 0, 0, false, cust));    // first label: start from depot (client 0)
		U.add(0);

		// for each city, an array with the index of the corresponding labels (for dominance)
		checkDom = new int[userParam.nbclients + 2];// 每个客户节点 被检查过“占优性”的节点有多少个
		ArrayList<Integer>[] city2labels = new ArrayList[userParam.nbclients + 2];
		for (i = 0; i < userParam.nbclients + 2; i++) {
			city2labels[i] = new ArrayList<Integer>();
			checkDom[i] = 0;  // index of the first label in city2labels that needs to be checked for dominance (last labels added)
		}
		city2labels[0].add(0);

		nbsol = 0;
		maxSol = 2 * nbRoute;
		while ((U.size() > 0) && (nbsol < maxSol)) {
			// second term if we want to limit to the first solutions encountered to speed up the SPPRC (perhaps not the BP)
			// remark: we'll keep only nbRoute, but we compute 2 x nbRoute!
			// It makes a huge difference => we'll keep the most negative ones
			// this is something to analyze further!  how many solutions to keep and which ones?
			// process one label => get the index AND remove it from U
			currentidx = U.pollFirst(); // 从队首弹出一个 待拓展的路径的下标
			current = labels.get(currentidx);

			// check for dominance 查看 当前城市的标签们 有没有 被占优的,要删除掉 被占优的标签
			// code not fully optimized:
			int l1, l2;
			boolean pathdom;
			label la1, la2;
			ArrayList<Integer> cleaning = new ArrayList<Integer>();
			// check for dominance between the labels added since the last time
			// we came here with this city and all the other ones
			// ?? checkDom?? here, why not directly use i=0
			for (i = checkDom[current.city]; i < city2labels[current.city].size(); i++) {
				for (j = 0; j < i; j++) {
					l1 = city2labels[current.city].get(i);
					l2 = city2labels[current.city].get(j);
					la1 = labels.get(l1);
					la2 = labels.get(l2);
					// could happen since we clean 'city2labels' thanks
					// to 'cleaning' only after the double loop
					if (!(la1.dominated || la2.dominated)) { // 两个标签暂时都没有被占优
						// Q1: 判断 标签2 是否被占优了
						pathdom = true;
						for (int k = 1; pathdom && (k < userParam.nbclients + 2); k++) {
							//la1没有访问节点k 或者 la2访问了节点k 、、说明  la1对应的路径比la2短
							// if pathdom=true, then it means la1来过的 la2必定也来过
							// 看书! la1 访问过的节点 少于 la2 访问过的节点
							pathdom = (!la1.vertexVisited[k] || la2.vertexVisited[k]);
						}
						if (pathdom && (la1.cost <= la2.cost) && (la1.tTime <= la2.tTime)
								&& (la1.demand <= la2.demand)) {
							labels.get(l2).dominated = true;// l2 被占优了
							U.remove((Integer) l2);
							cleaning.add(l2);
							pathdom = false; // ?? why bother to do this
							//System.out.print(" ###Remove"+l2);
						}

						// Q2: 判断 标签1 是否被占优了
						pathdom = true;
						for (int k = 1; pathdom && (k < userParam.nbclients + 2); k++) {
							//la2没有访问节点k或者la1访问了节点k
							//如果pathdom=true, that means la2 的沿途节点数 比 la1少, 因为la2访问过的,la1必然访问过
							pathdom = (!la2.vertexVisited[k] || la1.vertexVisited[k]);
						}
						if (pathdom && (la2.cost <= la1.cost) && (la2.tTime <= la1.tTime) && (la2.demand <= la1.demand)) {
							labels.get(l1).dominated = true;// l1被占优了
							U.remove(l1);
							cleaning.add(l1);
							//System.out.print(" ###Remove"+l1);
							j = city2labels[current.city].size();// ?? get out of this for loop, so I guess it's kind of speed-up
						}
					}
				}
			}

			for (Integer c : cleaning)
				city2labels[current.city].remove((Integer) c);   // a little bit confusing but ok since c is an Integer and not an int!

			cleaning = null;
			// for current.city, how many non-denominant labels have we checked?
			checkDom[current.city] = city2labels[current.city].size();  // update checkDom: all labels currently in city2labels were checked for dom.

			// expand REF
			if (!current.dominated) {// 当前的这个label没有被占优
				//System.out.println("Label "+current.city+" "+current.indexPrevLabel+" "+current.cost+" "+current.ttime+" "+current.dominated);
				if (current.city == userParam.nbclients + 1) { // ??shortest path candidate to the depot! 此时 不能再扩展路径了
					if (current.cost < -1e-7) {                // SP candidate for the column generation
						P.add(currentidx);// 当前标签没有被占优,将它加到 已处理的集合P 中
						nbsol = 0; // 数 集合P 中 未被占优的标签 的个数
						for (Integer labi : P) { // labi : label index
							label s = labels.get(labi);
							if (!s.dominated)
								nbsol++;
						}
					}
				} else {
					// if not the depot, we can consider extensions of the path
					for (i = 0; i < userParam.nbclients + 2; i++) { // try to reach Customer i
						// don't go back to a vertex already visited or along a forbidden edge
						// expand this label to some customer i
						if ((!current.vertexVisited[i]) &&
								(userParam.dist[current.city][i] < userParam.verybig - 1e-6)) {
							// ttime
							tt = (float) (current.tTime + userParam.ttime[current.city][i]
									+ userParam.s[current.city]);
							if (tt < userParam.a[i])// 提前到了,等到时间窗开放,才能服务
								tt = userParam.a[i];
							// demand
							d = current.demand + userParam.d[i];
							//System.out.println("  -- "+i+" d:"+d+" t:"+tt);

							//the potential next customer is feasible?
							if ((tt <= userParam.b[i]) && (d <= userParam.capacity)) {
								// satisfy the time window constraint and capacity constraint
								// current.city --> i
								idx = labels.size();
								boolean[] newCust = new boolean[userParam.nbclients + 2]; // vertextVisited
								System.arraycopy(current.vertexVisited, 0, newCust,
										0, userParam.nbclients + 2);
								newCust[i] = true;
								//speedup: third technique - Feillet 2004 as mentioned in Laporte's paper 、、??
								// current.city --> i --X--> j, so we marke all infeasible points j as 'visited'
								// then we could skip this point
								for (j = 1; j <= userParam.nbclients; j++) {
									if (!newCust[j]) {
										tt2 = (float) (tt + userParam.ttime[i][j] + userParam.s[i]);
										d2 = d + userParam.d[j];
										if ((tt2 > userParam.b[j]) || (d2 > userParam.capacity)) {
											newCust[j] = true;  // useless to visit this client , so marker it as 'visited'
										}
									}
								}
								// expand this label and then we obtain a new label( whose city is i) that is seen as unprocessed, so push it into U
								// label: city, indexPrevLabel, cost,tTime,demand, dominated, vertexVisited
								// !! 这里 current.cost + userParam.cost[current.city][i] 用到了 列生成算法 解主问题,得到对偶变量的值,然后更新了userParams.cost[][]
								labels.add(new label(i, currentidx, current.cost + userParam.cost[current.city][i], tt, d, false, newCust));    // first label: start from depot (client 0)
								if (!U.add((Integer) idx)) { // idx: index of this new label
									// only happens if there exists already a label at this vertex with the same cost, time and demand and visiting the same cities before
									// It can happen with some paths where the order of the cities is permuted
									// I guess, e,g, 0-1-2-3-0, 0-3-1-2-0
									labels.get(idx).dominated = true; // => we can forget this label and keep only the other one?? only keep the previous one
									// ?? but how can you do this? why?
								}
								else {
									city2labels[i].add(idx);
								}
							}
						}
					}
				}
			}
		}
		// clean
		checkDom = null;

		// filtering: find the path from depot to the destination
		Integer lab;
		i = 0;
		while ((i < nbRoute) && ((lab = P.pollFirst()) != null)) {
			label s = labels.get(lab);
			if (!s.dominated) {
				if (/*(i < nbroute / 2) ||*/ (s.cost < -1e-4)) {
					// System.out.println(s.cost);
					//        	if(s.cost > 0) {
					//        		System.out.println("warning >>>>>>>>>>>>>>>>>>>>");
					//        	}
					route newRoute = new route();
					newRoute.setcost(s.cost);
					newRoute.addcity(s.city);
					// 按照indexPreLabel回溯,找到最优解
					int path = s.indexPrevLabel;
					while (path >= 0) {// 这里和前面depot0的标签中indexPreLabel=-1相呼应
						newRoute.addcity(labels.get(path).city);
						path = labels.get(path).indexPrevLabel;
					}
					newRoute.switchpath();
					routes.add(newRoute);
					i++;
				}
			}

		}// end while
	}
}

  • 路径 route.java
package BranchAndPrice;

import java.util.ArrayList;

public class route implements Cloneable {
	public double cost, Q; // Q 表示 这条路径的使用次数的线性松弛版
	// first resource: cost (e.g. distance or strict travel time)

	public ArrayList<Integer> path;

	public route() { // non-param constructor
		this.path = new ArrayList<Integer>();
		this.cost = 0.0;
	}

	public route(int pathSize) {
		this.path = new ArrayList<Integer>(pathSize);
		this.cost = 0.0;
	}

	/*
	 * @update 2013. 6. 8
	 * @modify Geunho Kim
	 */
	// method for deep cloning // 深拷贝
	public route clone() throws CloneNotSupportedException {
		route route = (route) super.clone();// ?? 强制转型
		route.path = (ArrayList<Integer>) path.clone();
		return route;
	}

	public void removeCity(int city) {
		this.path.remove(Integer.valueOf(city));

	}

	public void addcity(int f_city, int city) {// 在f_city 之后,add city,
		int index = this.path.indexOf(f_city);
		this.path.add(index + 1, city);
	}

	public void addcity(int city) {
		this.path.add(city);
	}

	public void setcost(double c) {
		this.cost = c;
	}

	public double getcost() {
		return this.cost;
	}

	public void setQ(double a) {
		this.Q = a;
	}

	public double getQ() {
		return this.Q;
	}

	public ArrayList<Integer> getpath() {
		return this.path;
	}

	public void switchpath() {// 将path前后置换
		Integer swap;
		int nb = path.size() / 2;
		for (int i = 0; i < nb; i++) {
			swap = path.get(i);
			path.set(i, path.get(path.size() - 1 - i));// !!! not add
			path.set(path.size() - 1 - i, swap);
		}
	}
}
  • VRP问题的参数 paramsVRP.java
package BranchAndPrice;

// this class contains the inputs and methods to read the inputs
// for the Branch and Price CVRP with TW 
// ...I'm afraid that it is not pure OO code
// ...but it is not so bad

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;


public class paramsVRP {
	public int mvehic;
	public int nbclients;// number of clients
	public int capacity; // vehicle capacity
	public double[][] cost; // for the SPPRC subProblem
	public double[][] distBase; // 为dist存根,original distances for the Branch and Bound
	public double[][] dist; // distances that will be updated during the B&B before being used in the CG & SPPRC
	public double[][] ttime;// travel time
	public double[][] edges; // weight of each edge during branch and bound
	public double[] posx, posy, d, wval; // ?? what is wval
	public int[] a; // time windows: a=early, b=late, s=service
	public int[] b;
	public int[] s;
	public double verybig;
	public double speed; // vehicle's speed
	public double gap;
	public double maxlength;
	public boolean serviceInTW;
	String[] citieslab;

	public paramsVRP() {
		gap = 0.00000000001;
		serviceInTW = false;
		nbclients = 100;
		speed = 1;
		mvehic = 0;
		verybig = 1E10;
	}

	public void initParams(String inputPath) throws IOException {
		int i, j;

		try {
			/**
			 * @update 2013. 6. 12
			 * @modify Geunho Kim
			 *
			 *  for Hadoop distributed file system
			 */

			BufferedReader br = new BufferedReader(new FileReader(inputPath));

			String line = new String();

			// //
			// for local file system
			// BufferedReader br = new BufferedReader(new FileReader(inputPath));

			for (i = 0; i < 5; i++)
				line = br.readLine();

			String[] tokens = line.split("\\s+");
			mvehic = Integer.parseInt(tokens[1]);
			capacity = Integer.parseInt(tokens[2]);

			citieslab = new String[nbclients + 2];
			d = new double[nbclients + 2]; // demand
			a = new int[nbclients + 2]; // ready time
			b = new int[nbclients + 2];// due time
			s = new int[nbclients + 2];// service time
			posx = new double[nbclients + 2];
			posy = new double[nbclients + 2];
			distBase = new double[nbclients + 2][nbclients + 2];
			cost = new double[nbclients + 2][nbclients + 2];
			dist = new double[nbclients + 2][nbclients + 2];
			ttime = new double[nbclients + 2][nbclients + 2];

			for (i = 0; i < 4; i++)
				line = br.readLine();

			for (i = 0; i < nbclients + 1; i++) {
				line = br.readLine();
				//System.out.println(line);
				tokens = line.split("\\s+");
				citieslab[i] = tokens[1]; // customer number
				posx[i] = Double.parseDouble(tokens[2]); // x coordinate
				posy[i] = Double.parseDouble(tokens[3]); // y coordinate
				d[i] = Double.parseDouble(tokens[4]); // demand
				a[i] = Integer.parseInt(tokens[5]); // ready time
				b[i] = Integer.parseInt(tokens[6]); // due time
				s[i] = Integer.parseInt(tokens[7]); // service
				// check if the service should be done before due time
				if (serviceInTW) // ??
					b[i] -= s[i];
			}
			br.close();

			// second depot : copy of the first one for arrival
			citieslab[nbclients + 1] = citieslab[0];
			d[nbclients + 1] = 0.0;
			a[nbclients + 1] = a[0];
			b[nbclients + 1] = b[0];
			s[nbclients + 1] = 0;// !!
			posx[nbclients + 1] = posx[0];
			posy[nbclients + 1] = posy[0];

			// ---- distances
			double max;
			maxlength = 0.0;// 一个路径长度的上限,用于列生成算法
			for (i = 0; i < nbclients + 2; i++) {
				max = 0.0;
				for (j = 0; j < nbclients + 2; j++) {
					// dist[i][j]=Math.round(10*Math.sqrt((posx[i]-posx[j])*(posx[i]-posx[j])+(posy[i]-posy[j])*(posy[i]-posy[j])))/10.0;
					distBase[i][j] = ((int) (10 * Math
							.sqrt((posx[i] - posx[j]) * (posx[i] - posx[j])
									+ (posy[i] - posy[j]) * (posy[i] - posy[j])))) / 10.0; //只保留到小数点后一位
					// truncate to get the same results as in Solomon
					if (max < distBase[i][j]) max = distBase[i][j];
				}
				maxlength += max; // a route with a length longer than this is not
				// possible (we need it to check the feasibility of
				// the Column Gen sol.
			}

			// some edges are not available to pass in some direction
			for (i = 0; i < nbclients + 2; i++) {
				distBase[i][0] = verybig;// any node can't go back to Depot 0
				distBase[nbclients + 1][i] = verybig; // Depot nbclients+1 cannot visit any other node
				distBase[i][i] = verybig; // any node can't form a loop on its own
			}
			/*
			 * for(i = 0; i < 20; i++)
			 *   distBase[10][i] = verybig;
			 * for(i = 21; i < nbclients+2; i++)
			 *   distBase[10][i] = verybig;
			 * for(i = 0; i < 10; i++)
			 *   distBase[i][20] = verybig;
			 * for(i = 11; i < nbclients+2; i++)
			 *   distBase[i][20] = verybig;
			 * distBase[20][10] = verybig;
			 */
			for (i = 0; i < nbclients + 2; i++)
				for (j = 0; j < nbclients + 2; j++) {
					dist[i][j] = distBase[i][j];
				}


			// ---- time
			for (i = 0; i < nbclients + 2; i++)
				for (j = 0; j < nbclients + 2; j++)
					ttime[i][j] = distBase[i][j] / speed;

			// ------- cost
			for (j = 0; j < nbclients + 2; j++) {
				cost[0][j] = dist[0][j];
				cost[j][nbclients + 1] = dist[j][nbclients + 1];
			}
			// !!cost for the other edges are determined during column generation

		} catch (IOException e) {
			System.err.println("Error: " + e);
		}

		wval = new double[nbclients + 2];
		for (i = 1; i < nbclients + 2; i++) // ??
			wval[i] = 0.0;

		edges = new double[nbclients + 2][nbclients + 2];

	}
}
  • 7
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是用Python实现标签算法求解VRPTW问题代码,其中采用深度优先搜索的方式: ```python import numpy as np # 车辆容量 CAPACITY = 100 # 客户点信息(坐标、需求量、时间窗口) CUSTOMER_POINTS = np.array([ [40, 50, 0, 0, 8], [45, 68, 10, 12, 10], [60, 70, 20, 25, 12], [70, 40, 20, 30, 15], [66, 20, 30, 40, 20], [30, 30, 35, 50, 5], [35, 20, 40, 45, 3], [20, 25, 45, 50, 5], [50, 25, 50, 55, 8], [55, 45, 60, 70, 10] ]) # 标签结构定义:(到达时间, 已服务客户, 车辆位置) LABEL_TUPLE = (int, list, int) def label_extend(current_state, current_label): """ 标签扩展函数,根据当前状态和标签信息,生成所有可能的下一状态,并计算其对应的标签信息 :param current_state: 当前状态,即当前已服务的客户点 :param current_label: 当前标签信息,即当前状态对应的标签信息 :return: 下一状态和对应的标签信息 """ current_time, served_customers, current_position = current_label next_states = [] for i in range(len(CUSTOMER_POINTS)): if i not in served_customers and CAPACITY >= CUSTOMER_POINTS[i][3]: # 计算到达下一客户点的时间 distance = np.sqrt((CUSTOMER_POINTS[i][0] - CUSTOMER_POINTS[current_state][0]) ** 2 + (CUSTOMER_POINTS[i][1] - CUSTOMER_POINTS[current_state][1]) ** 2) arrival_time = current_time + distance if arrival_time <= CUSTOMER_POINTS[i][4]: # 状态扩展 next_state = i next_served_customers = served_customers.copy() next_served_customers.append(i) next_position = i next_label = (arrival_time, next_served_customers, next_position) next_states.append((next_state, next_label)) return next_states def heuristic_function(state, label): """ 启发函数,评估当前状态的优劣程度 :param state: 当前状态 :param label: 当前标签信息 :return: 启发函数值 """ return label[0] def dfs_search(): """ 标签算法主函数,按照深度优先的方式搜索状态空间,直到找到满足所有约束条件的解 :return: 找到的解 """ # 初始化搜索树 root_state = 0 root_label = (0, [0], 0) search_tree = {root_state: [root_label]} # 深度优先搜索 best_label = None stack = [(root_state, root_label)] while stack: current_state, current_label = stack.pop() if len(current_label[1]) == len(CUSTOMER_POINTS): # 找到解 if best_label is None or heuristic_function(current_state, current_label) < heuristic_function(best_label[0], best_label[1]): best_label = (current_state, current_label) else: # 扩展状态,并加入搜索树 next_states = label_extend(current_state, current_label) for next_state, next_label in next_states: if next_state not in search_tree: search_tree[next_state] = [next_label] stack.append((next_state, next_label)) else: if heuristic_function(next_state, next_label) < heuristic_function(next_state, search_tree[next_state][-1]): search_tree[next_state].append(next_label) stack.append((next_state, next_label)) return best_label if __name__ == '__main__': best_label = dfs_search() print('找到的最优解:', best_label) ``` 这个代码实现了深度优先搜索,可以根据需要进行修改以实现广度优先搜索或其他搜索算法
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值