热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

matlab分支定界法linprog_运筹学教学|分支定界法解带时间窗的车辆路径规划问题(附代码及详细注释)...

历尽千辛万苦,外加外援帮助,本辣鸡小编终于搞定了这个大坑-用分支定界法(Branchandbound,B&B)解带时间窗的车辆路径规划问题(VRPTW)

00ce907d6bf32419a405f367cbc92f2a.png

历尽千辛万苦,外加外援帮助,本辣鸡小编终于搞定了这个大坑-用分支定界法(Branch and bound, B&B)解带时间窗的车辆路径规划问题(VRPTW)。

预备知识

前面的推文中有提到过,分支定界法是一种精确解算法,之前推文“运筹学教学|分枝定界求解旅行商问题”中对于分支定界的基本思想进行了详细的阐述,有不记得的小伙伴可以点击上面的链接传送到之前推文。

带时间窗的车辆路径规划问题(下简称:VRPTW)在之前的推文中已经被详细的介绍过了,为了方便读者的阅读,我们在这里给出传送门

干货|十分钟快速掌握CPLEX求解VRPTW数学模型(附JAVA代码及CPLEX安装流程)

除此之外还要先学习一种数据结构叫做优先队列。优先队列(priority queue)是一种常用的数据结构,在这种数据结构中,队头永远是存储优先级最高的元素,取队头和插入元素的操作的时间复杂度都是O(logn)。在JAVA和C++中都内置了这一种数据结构,因此,亲爱的读者们不要害怕。当然如果有代码实现能力强的读者想要手工实现优先队列,也是可以的,想要学习优先队列以事先学习堆(heap)这种数据结构,可以完美的实现优先队列的功能。

当你仔细的阅读了上面两篇推文并理解了优先队列的原理之后,小编相信聪明的你一定不会对于接下来要讲的内容感到陌生。

代码以及解释

代码共分为4个类包括:

  • BaB_Vrptw :主类,用于建模以及分支定界求解VRPTW。

  • Check : 解的可行性判断

  • Data : 定义参数

  • Node : 定义分支定界的节点

01

Data 类

Data类的作用就是读入数据以及数据预处理,在这里我们便不做过多的解释,为了方便后面的阅读以及篇幅限制,我们在这里便不对其进行展开描述,代码中的注释对于各个变量含义有较为详细的介绍。但是由于之后的程序会调用这些变量,我们便首先讲解这个类。

   class Data{  
   int vertex_num;         //所有点集合n(包括配送中心和客户点,首尾(0和n)为配送中心)  
   double E;               //配送中心时间窗开始时间  
   double  L;              //配送中心时间窗结束时间  
   int veh_num;            //车辆数  
   double cap;             //车辆载荷  
   int[][] vertexs;        //所有点的坐标x,y  
   int[] demands;          //需求量  
   int[] vehicles;         //车辆编号  
   double[] a;             //时间窗开始时间【a[i],b[i]】  
   double[] b;             //时间窗结束时间【a[i],b[i]】  
   double[] s;             //客户点的服务时间  
   int[][] arcs;           //arcs[i][j]表示i到j点的弧  
   double[][] dist;        //距离矩阵,满足三角关系,暂用距离表示花费 C[i][j]=dist[i][j]  
   double gap= 1e-6;     // 一个小数,表示精读
   double big_num = 100000;  // 无穷大
   //截断小数3.26434-->3.2  
   public double double_truncate(double v){  
       int iv = (int) v;  
       if(iv&#43;1 - v <&#61; gap)  
           return iv&#43;1;  
       double dv &#61; (v - iv) * 10;  
       int idv &#61; (int) dv;  
       double rv &#61; iv &#43; idv / 10.0;  
       return rv;  
   }    
   public Data() {  
       super();  
   }  
   //函数功能&#xff1a;从txt文件中读取数据并初始化参数  
   public void Read_data(String path,Data data,int vertexnum) throws Exception{  
       String line &#61; null;  
       String[] substr &#61; null;  
       Scanner cin &#61; new Scanner(new BufferedReader(new FileReader(path)));  //读取文件  
       for(int i &#61;0; i <4;i&#43;&#43;){  
           line &#61; cin.nextLine();  //读取一行  
       }  
       line &#61; cin.nextLine();  
       line.trim(); //返回调用字符串对象的一个副本&#xff0c;删除起始和结尾的空格  
       substr &#61; line.split(("\\s&#43;")); //以空格为标志将字符串拆分  
       //初始化参数  
       data.vertex_num &#61; vertexnum;  
       data.veh_num &#61; Integer.parseInt(substr[1]);  
       data.cap &#61; Integer.parseInt(substr[2]);  
       data.vertexs &#61;new int[data.vertex_num][2];              //所有点的坐标x,y  
       data.demands &#61; new int[data.vertex_num];                    //需求量  
       data.vehicles &#61; new int[data.veh_num];                  //车辆编号  
       data.a &#61; new double[data.vertex_num];                       //时间窗开始时间  
       data.b &#61; new double[data.vertex_num];                       //时间窗结束时间  
       data.s &#61; new double[data.vertex_num];                       //服务时间  
       data.arcs &#61; new int[data.vertex_num][data.vertex_num];  
       //距离矩阵,满足三角关系,用距离表示cost  
       data.dist &#61; new double[data.vertex_num][data.vertex_num];  
       for(int i &#61;0; i <4;i&#43;&#43;){  
           line &#61; cin.nextLine();  
       }  
       //读取vetexnum-1行数据  
       for (int i &#61; 0; i            line &#61; cin.nextLine();  
           line.trim();  
           substr &#61; line.split("\\s&#43;");  
           data.vertexs[i][0] &#61; Integer.parseInt(substr[2]);  
           data.vertexs[i][1] &#61; Integer.parseInt(substr[3]);  
           data.demands[i] &#61; Integer.parseInt(substr[4]);  
           data.a[i] &#61; Integer.parseInt(substr[5]);  
           data.b[i] &#61; Integer.parseInt(substr[6]);  
           data.s[i] &#61; Integer.parseInt(substr[7]);  
       }  
       cin.close();//关闭流  
       //初始化配送中心参数  
       data.vertexs[data.vertex_num-1] &#61; data.vertexs[0];  
       data.demands[data.vertex_num-1] &#61; 0;  
       data.a[data.vertex_num-1] &#61; data.a[0];  
       data.b[data.vertex_num-1] &#61; data.b[0];  
       data.E &#61; data.a[0];  
       data.L &#61; data.b[0];  
       data.s[data.vertex_num-1] &#61; 0;        
       double min1 &#61; 1e15;  
       double min2 &#61; 1e15;  
       //距离矩阵初始化  
       for (int i &#61; 0; i            for (int j &#61; 0; j                if (i &#61;&#61; j) {  
                   data.dist[i][j] &#61; 0;  
                   continue;  
               }  
               data.dist[i][j] &#61;  
                   Math.sqrt((data.vertexs[i][0]-data.vertexs[j][0])  
                           *(data.vertexs[i][0]-data.vertexs[j][0])&#43;  
                   (data.vertexs[i][1]-data.vertexs[j][1])  
                   *(data.vertexs[i][1]-data.vertexs[j][1]));  
               data.dist[i][j]&#61;data.double_truncate(data.dist[i][j]);  
           }  
       }  
       data.dist[0][data.vertex_num-1] &#61; 0;  
       data.dist[data.vertex_num-1][0] &#61; 0;  
       //距离矩阵满足三角关系  
       for (int  k &#61; 0; k            for (int i &#61; 0; i                for (int j &#61; 0; j                    if (data.dist[i][j] > data.dist[i][k] &#43; data.dist[k][j]) {  
                       data.dist[i][j] &#61; data.dist[i][k] &#43; data.dist[k][j];  
                   }  
               }  
           }  
       }  
       //初始化为完全图  
       for (int i &#61; 0; i            for (int j &#61; 0; j                if (i !&#61; j) {  
                   data.arcs[i][j] &#61; 1;  
               }  
               else {  
                   data.arcs[i][j] &#61; 0;  
               }  
           }  
       }  
       //除去不符合时间窗和容量约束的边  
       for (int i &#61; 0; i            for (int j &#61; 0; j                if (i &#61;&#61; j) {  
                   continue;  
               }  
               if (data.a[i]&#43;data.s[i]&#43;data.dist[i][j]>data.b[j] ||  
                       data.demands[i]&#43;data.demands[j]>data.cap) {  
                   data.arcs[i][j] &#61; 0;  
               }  
               if (data.a[0]&#43;data.s[i]&#43;data.dist[0][i]&#43;data.dist[i][data.vertex_num-1]>  
               data.b[data.vertex_num-1]) {  
                   System.out.println("the calculating example is false");  

               }  
           }  
       }  
       for (int i &#61; 1; i            if (data.b[i] - data.dist[0][i]                min1 &#61; data.b[i] - data.dist[0][i];  
           }  
           if (data.a[i] &#43; data.s[i] &#43; data.dist[i][data.vertex_num-1]                min2 &#61; data.a[i] &#43; data.s[i] &#43; data.dist[i][data.vertex_num-1];  
           }  
       }  
       if (data.E > min1 || data.L            System.out.println("Duration false!");  
           System.exit(0);//终止程序  
       }  
       //初始化配送中心0&#xff0c;n&#43;1两点的参数  
       data.arcs[data.vertex_num-1][0] &#61; 0;  
       data.arcs[0][data.vertex_num-1] &#61; 1;  
       for (int i &#61; 1; i            data.arcs[data.vertex_num-1][i] &#61; 0;  
       }  
       for (int i &#61; 1; i            data.arcs[i][0] &#61; 0;  
       }  
   }  
}  

02

Node类

Node类的主要作用是记录分支节点&#xff0c;下面一段代码是Node类定义的对象

Data data;  
   int d;  
   double node_cost;               //目标值object  
   double[][][]lp_x;//记录lp解  
   int[][][] node_x_map;//node_xij&#61;1时,node_x_mapijk&#61;1表示必须访问&#xff0c;node_x_mapijk&#61;0表示不能访问  
   int[][] node_x;//0表示弧可以访问&#xff0c;1表示必须访问&#xff0c;-1表示不能访问  
   ArrayList> node_routes;      //定义车辆路径链表  
   ArrayList> node_servetimes;   //定义花费时间链表

Node类的初始化如下&#xff0c;注意新生成的node_cost 的初始值是无穷大&#xff0c;因为在没有操作的情况下&#xff0c;这是一个非法解。

public Node(Data data) {  
   super();  
   this.data &#61; data;  
   node_cost &#61; data.big_num;  
   lp_x &#61; new double [data.vertex_num][data.vertex_num][data.veh_num];  
   node_x_map &#61; new int[data.vertex_num][data.vertex_num][data.veh_num];  
   node_x &#61; new int[data.vertex_num][data.vertex_num];  
   node_routes &#61; new ArrayList>();  
   node_servetimes &#61; new ArrayList>();  
}  

由于要进行多次的生成节点&#xff0c;为了方便&#xff0c;我们设置了一个函数note_copy()来完成这项操作以及两个节点比较大小的函数。

public Node note_copy() {  
   Node new_node &#61; new Node(data);  
   new_node.d &#61; d;  
   new_node.node_cost &#61; node_cost;  
   for (int i &#61; 0; i        for (int j &#61; 0; j            new_node.lp_x[i][j] &#61; lp_x[i][j].clone();  
       }  
   }  
   for (int i &#61; 0; i        new_node.node_x[i] &#61; node_x[i].clone();  
   }  
   for (int i &#61; 0; i        for (int j &#61; 0; j            new_node.node_x_map[i][j] &#61; node_x_map[i][j].clone();  
       }  
   }  
   for (int i &#61; 0; i        new_node.node_routes.add((ArrayList) node_routes.get(i).clone());  
   }  for (int i &#61; 0; i        new_node.node_servetimes.add((ArrayList) node_servetimes.get(i).clone());  
   }  return new_node;  
}  public int compareTo(Object o){  
   Node node &#61; (Node) o;  if(node_cost }  

03

BaB_Vrptw类

    这是整个程序中最重要的一个部分&#xff0c;因此本文将花费大篇幅来讲解这个问题(&#xff61;&#xff65;∀&#xff65;)&#xff89;&#xff9e;嗨。看程序先看什么&#xff1f;答案是-主函数。

public static void main(String[] args) throws Exception {
       Data data &#61; new Data();
       int vetexnum &#61; 102;//所有点个数&#xff0c;包括0&#xff0c;n&#43;1两个配送中心点
       //读入不同的文件前要手动修改vetexnum参数&#xff0c;参数值等于所有点个数,包括配送中心
       String path &#61; "data/c102.txt";//算例地址
       data.Read_data(path,data,vetexnum);
       System.out.println("input succesfully");
       System.out.println("cplex procedure###########################");
       BaB_Vrptw lp &#61; new BaB_Vrptw(data);
       double cplex_time1 &#61; System.nanoTime();
       //删除未用的车辆&#xff0c;缩小解空间
       lp&#61;lp.init(lp);
       System.out.println(":   "&#43;lp.data.veh_num);
       lp.branch_and_bound(lp);
       Check check &#61; new Check(lp);
       check.fesible();
       double cplex_time2 &#61; System.nanoTime();
       double cplex_time &#61; (cplex_time2 - cplex_time1) / 1e9;//求解时间&#xff0c;单位s
       System.out.println("cplex_time " &#43; cplex_time &#43; " bestcost " &#43; lp.cur_best);
       for (int i &#61; 0; i            ArrayList r &#61; lp.best_note.node_routes.get(i);
           System.out.println();for (int j &#61; 0; j                System.out.print(r.get(j)&#43;" ");
           }
       }
   }

上面的函数就是主函数&#xff0c;前面的11行都是数据读入的内容&#xff0c;相信大家都能看懂&#xff0c;在这里就不做赘述&#xff0c;遇到的第一个操作init&#xff0c;这个函数的作用是确定有合法解的最小车辆数量&#xff0c;由于直接求解&#xff0c;解空间太大&#xff0c;且有很多车辆不能使用&#xff0c;因此&#xff0c;我们删去无用的车辆&#xff0c;来缩小解空间(这是一个小优化&#xff0c;能够加快程序速度)

public BaB_Vrptw init(BaB_Vrptw lp) throws IloException {  
       lp.build_model();  
       if (lp.model.solve()) {  
           lp.get_value();  
           int aa&#61;0;  
           for (int i &#61; 0; i                if (lp.routes.get(i).size()&#61;&#61;2) {  
                   aa&#43;&#43;;  
               }  
           }  
           System.out.println(aa);  
           if (aa&#61;&#61;0) {  
               data.veh_num -&#61;1;  
               lp.model.clearModel();  
               lp &#61; new BaB_Vrptw(data);  
               return init(lp);  
           }else {  
               data.veh_num -&#61;aa;  
               lp.model.clearModel();  
               lp &#61; new BaB_Vrptw(data);  
               return init(lp);  
           }  
       }else {  
           data.veh_num &#43;&#61;1;  
           System.out.println("vehicle number: "&#43;data.veh_num);  
           lp.model.clearModel();  
           lp &#61; new BaB_Vrptw(data);  
           lp.build_model();  
           if (lp.model.solve()) {  
               lp.get_value();  
               return lp;  
           }else {  
               System.out.println("error init");  
               return null;  
           }  
       }  
   }  

如上面的程序所示&#xff0c;具体的做法就是建立一个松弛了的cplex模型&#xff0c;并计算使用的车辆数&#xff0c;如果有aa辆未使用车辆就减少aa辆可用车辆&#xff0c;否则减少一辆直到没有可行解。当然&#xff0c;最后我们可使用的车辆是最少的车辆啦~

松弛的模型代码如下&#xff0c; 这就是之前“干货|十分钟快速掌握CPLEX求解VRPTW数学模型(附JAVA代码及CPLEX安装流程)”中的模型把x_ijk的整数约束去掉得到的。

private void build_model() throws IloException {  
       //model  
       model &#61; new IloCplex();  
       model.setOut(null);  
//      model.setParam(IloCplex.DoubleParam.EpOpt, 1e-9);  
//      model.setParam(IloCplex.DoubleParam.EpGap, 1e-9);  
       //variables  
       x &#61; new IloNumVar[data.vertex_num][data.vertex_num][data.veh_num];  
       w &#61; new IloNumVar[data.vertex_num][data.veh_num];               //车辆访问点的时间  
       //定义cplex变量x和w的数据类型及取值范围  
       for (int i &#61; 0; i            for (int k &#61; 0; k        w[i][k] &#61; model.numVar(0, 1e15, IloNumVarType.Float, "w" &#43; i &#43; "," &#43; k);  
           }  
           for (int j &#61; 0; j                if (data.arcs[i][j]&#61;&#61;0) {  
                   x[i][j] &#61; null;  
               }  
               else{  
                   //Xijk,公式(10)-(11)  
                   for (int k &#61; 0; k    x[i][j][k] &#61; model.numVar(0, 1, IloNumVarType.Float, "x" &#43; i &#43; "," &#43; j &#43; "," &#43; k);  
                   }  
               }  
           }  
       }  
       //加入目标函数  
       //公式(1)  
       IloNumExpr obj &#61; model.numExpr();  
       for(int i &#61; 0; i            for(int j &#61; 0; j                if (data.arcs[i][j]&#61;&#61;0) {  
                   continue;  
               }  
               for(int k &#61; 0; k                    obj &#61; model.sum(obj, model.prod(data.dist[i][j], x[i][j][k]));  
               }  
           }  
       }  
       model.addMinimize(obj);  
       //加入约束  
       //公式(2)  
       for(int i&#61; 1; i            IloNumExpr expr1 &#61; model.numExpr();  
           for (int k &#61; 0; k                for (int j &#61; 1; j                    if (data.arcs[i][j]&#61;&#61;1) {  
                       expr1 &#61; model.sum(expr1, x[i][j][k]);  
                   }  
               }  
           }  
           model.addEq(expr1, 1);  
       }  
       //公式(3)  
       for (int k &#61; 0; k            IloNumExpr expr2 &#61; model.numExpr();  
           for (int j &#61; 1; j                if (data.arcs[0][j]&#61;&#61;1) {  
                   expr2 &#61; model.sum(expr2, x[0][j][k]);  
               }  
           }  
           model.addEq(expr2, 1);  
       }  
       //公式(4)  
       for (int k &#61; 0; k            for (int j &#61; 1; j                IloNumExpr expr3 &#61; model.numExpr();  
               IloNumExpr subExpr1 &#61; model.numExpr();  
               IloNumExpr subExpr2 &#61; model.numExpr();  
               for (int i &#61; 0; i                    if (data.arcs[i][j]&#61;&#61;1) {  
                       subExpr1 &#61; model.sum(subExpr1,x[i][j][k]);  
                   }  
                   if (data.arcs[j][i]&#61;&#61;1) {  
                       subExpr2 &#61; model.sum(subExpr2,x[j][i][k]);  
                   }  
               }  
               expr3 &#61; model.sum(subExpr1,model.prod(-1, subExpr2));  
               model.addEq(expr3, 0);  
           }  
       }  
       //公式(5)  
       for (int k &#61; 0; k            IloNumExpr expr4 &#61; model.numExpr();  
           for (int i &#61; 0; i                if (data.arcs[i][data.vertex_num-1]&#61;&#61;1) {  
                   expr4 &#61; model.sum(expr4,x[i][data.vertex_num-1][k]);  
               }  
           }  
           model.addEq(expr4, 1);  
       }  
       //公式(6)  
       double M &#61; 1e5;  
       for (int k &#61; 0; k            for (int i &#61; 0; i                for (int j &#61; 0; j                    if (data.arcs[i][j] &#61;&#61; 1) {  
                       IloNumExpr expr5 &#61; model.numExpr();  
                       IloNumExpr expr6 &#61; model.numExpr();  
                       expr5 &#61; model.sum(w[i][k], data.s[i]&#43;data.dist[i][j]);  
                       expr5 &#61; model.sum(expr5,model.prod(-1, w[j][k]));  
                       expr6 &#61; model.prod(M,model.sum(1,model.prod(-1, x[i][j][k])));  
                       model.addLe(expr5, expr6);  
                   }  
               }  
           }  
       }  
       //公式(7)  
       for (int k &#61; 0; k            for (int i &#61; 1; i                IloNumExpr expr7 &#61; model.numExpr();  
               for (int j &#61; 0; j                    if (data.arcs[i][j] &#61;&#61; 1) {  
                       expr7 &#61; model.sum(expr7,x[i][j][k]);  
                   }  
               }  
               model.addLe(model.prod(data.a[i], expr7), w[i][k]);  
               model.addLe(w[i][k], model.prod(data.b[i], expr7));  
           }  
       }  
       //公式(8)  
       for (int k &#61; 0; k            model.addLe(data.E, w[0][k]);  
           model.addLe(data.E, w[data.vertex_num-1][k]);  
           model.addLe(w[0][k], data.L);  
           model.addLe(w[data.vertex_num-1][k], data.L);  
       }  
       //公式(9)  
       for (int k &#61; 0; k            IloNumExpr expr8 &#61; model.numExpr();  
           for (int i &#61; 1; i                IloNumExpr expr9 &#61; model.numExpr();  
               for (int j &#61; 0; j                    if (data.arcs[i][j] &#61;&#61; 1) {  
                       expr9&#61;model.sum(expr9,x[i][j][k]);  
                   }  
               }  
               expr8 &#61; model.sum(expr8,model.prod(data.demands[i],expr9));  
           }  
           model.addLe(expr8, data.cap);  
       }  
   }  

之后就是branch and bound过程&#xff0c;在这里&#xff0c;就是最重点的环节了&#xff0c;先说一下我们的定界方法&#xff0c;把VRPTW的数学模型松弛的成一个线性规划问题可以求解出VRPTW问题的一个下界&#xff0c;分支的原则就是对于一个选定的x_ijk&#xff0c;且0

//  找到要分支的弧  
   public int[] find_arc(double[][][] x) {  
       int record[] &#61; new int[3];//记录分支顶点  
       for (int i &#61; 0; i            for (int j &#61; 0; j                if (data.arcs[i][j]>0.5) {  
                   for (int k &#61; 0; k                        //若该弧值为0或1&#xff0c;则继续  
                       if (is_one_zero(x[i][j][k])) {  
                           continue;  
                       }  
//                      cur_dif &#61; get_dif(x[i][j][k]);  
                       record[0] &#61; i;  
                       record[1] &#61; j;  
                       record[2] &#61; k;  
                       return record;  
                   }  
               }  
           }  
       }  
       record[0] &#61; -1;  
       record[1] &#61; -1;  
       record[2] &#61; -1;  
       return record;  
   }  

分支定界的流程是&#xff1a;

  1. 确定一个下界(初始解LB)&#xff0c;上界定为无穷大UB。

  2. 把初始问题构建一个节点加入优先队列(因为是优先队列&#xff0c;所以使用best first sloution&#xff0c;也就是每一次最好的目标值最前搜索)。

  3. 判断队列是否为空&#xff0c;如果为空跳转至7&#xff0c;否则取出并弹出队首元素&#xff0c;计算该节点的目标值P。

  4. 如果P > UB&#xff0c;返回3。否则判断当前节点是否是合法解(对于任意i,j,k,x_ijk均为整数)&#xff0c;如果是&#xff0c;跳转5否则跳转6。

  5. 如果P

  6. 设置两个子节点L, R。L&#xff0c;R的建立方式如上&#xff0c;如果L的目标值L.P <&#61; UB&#xff0c;把L加入队列&#xff0c;如果R的目标值R.P <&#61; UB&#xff0c;把R加入队列。返回3.

  7. 结束&#xff0c;返回记录的最优节点BS。如果BS为空则无解。

public void branch_and_bound(BaB_Vrptw lp) throws IloException {  
       cur_best &#61; 3000;//设置上界  
       deep&#61;0;  
       record_arc &#61; new int[3];  
       node1 &#61; new Node(data);  
       best_note &#61; null;  
       queue &#61; new PriorityQueue();  //初始解(非法解)  for (int i &#61; 0; i            ArrayList r &#61; lp.routes.get(i);  
           System.out.println();  for (int j &#61; 0; j                System.out.print(r.get(j)&#43;" ");  
           }  
       }  
       lp.copy_lp_to_node(lp, node1);  //      node1.node_cost &#61; lp.cost;  //      node1.lp_x &#61; lp.x_map.clone();  //      node1.node_routes &#61;lp.routes;  //      node1.node_servetimes &#61; lp.servetimes;  
       node2 &#61; node1.note_copy();  
       deep&#61;0;  
       node1.d&#61;deep;  
       queue.add(node1);  //branch and bound过程  while (!queue.isEmpty()) {  
           Node node &#61; queue.poll();  //某支最优解大于当前最好可行解&#xff0c;删除  if (doubleCompare(node.node_cost, cur_best)>0) {  continue;  
           }else {  
               record_arc &#61; lp.find_arc(node.lp_x);  //某支的合法解,0,1组合的解,当前分支最好解  if (record_arc[0]&#61;&#61;-1) {  //比当前最好解好&#xff0c;更新当前解  if (doubleCompare(node.node_cost, cur_best)&#61;&#61;-1) {  
                       lp.cur_best &#61; node.node_cost;  
                       System.out.println(node.d&#43;"  cur_best:"&#43;cur_best);  
                       lp.best_note &#61; node;  
                   }  continue;  
               }else {//可以分支  
                   node1 &#61; lp.branch_left_arc(lp, node, record_arc);//左支  
                   node2 &#61; lp.branch_right_arc(lp, node, record_arc);//右支  if (node1!&#61;null && doubleCompare(node1.node_cost, cur_best)<&#61;0) {  
                       queue.add(node1);  
                   }  if (node2!&#61;null && doubleCompare(node2.node_cost, cur_best)<&#61;0) {  
                       queue.add(node2);  
                   }  
               }  
           }  
       }  
   }  //设置左支  public Node branch_left_arc(BaB_Vrptw lp,Node father_node,int[] record) throws IloException {  if (record[0] &#61;&#61; -1) {  return null;  
       }  
       Node new_node &#61; new Node(data);  
       new_node &#61; father_node.note_copy();  
       new_node.node_x[record[0]][record[1]] &#61; -1;  for (int k &#61; 0; k            new_node.node_x_map[record[0]][record[1]][k]&#61;0;  
       }  //      new_node.node_x_map[record[0]][record[1]][record[2]]&#61;-1;  //设置左支  
       lp.set_bound(new_node);  if (lp.model.solve()) {  
           lp.get_value();  
           deep&#43;&#43;;  
           new_node.d&#61;deep;  
           lp.copy_lp_to_node(lp, new_node);  
           System.out.println(new_node.d&#43;" "&#43;lp.cost);  
       }else {  
           new_node.node_cost &#61; data.big_num;  
       }  return new_node;  
}  //设置右支  public Node branch_right_arc(BaB_Vrptw lp,Node father_node,int[] record) throws IloException {  if (record[0] &#61;&#61; -1) {  return null;  
       }  
       Node new_node &#61; new Node(data);  
       new_node &#61; father_node.note_copy();  
       new_node.node_x[record[0]][record[1]] &#61; 1;  //      new_node.node_x_map[record[0]][record[1]][record[2]]&#61;1;  for (int k &#61; 0; k                new_node.node_x_map[record[0]][record[1]][k]&#61;1;  
           }else {  
               new_node.node_x_map[record[0]][record[1]][k]&#61;0;  
           }  
       }  //设置右支  
       lp.set_bound(new_node);  if (lp.model.solve()) {  
           lp.get_value();  
           deep&#43;&#43;;  
           new_node.d&#61;deep;  
           System.out.println(new_node.d&#43;" right: "&#43;lp.cost);  
           lp.copy_lp_to_node(lp, new_node);  
       }else {  
           new_node.node_cost &#61; data.big_num;  
       }  return new_node;  
   }  //找到需要分支的支点位置  

这样就完美的利用branch and bound解决了VRPTW。诶&#xff0c;等等&#xff0c;完美么&#xff1f;是不是忘了点什么&#xff1f;解的合法性有没有检验呢&#xff1f;

为了检验我们所求的解是不是合法的&#xff0c;我们利用迟迟没出面的Check类来检查这个问题。

01

Check类

Check类存在的目的&#xff0c;主要是检验解的可行性&#xff0c;包括解是否满足车辆数量约束&#xff0c;是否满足容量约束&#xff0c;时间窗约束等等。

包括函数

double_compare(v1, v2): 比较两个数大小 v1 v2 &#43; eps 返回1&#xff0c; 两数相等返回0。

fesible()&#xff1a;判断解的可行性&#xff0c;包括车辆数量可行性&#xff0c;车辆载荷可行性&#xff0c;时间窗、车容量可行性判断。

class Check{  
   double epsilon &#61; 0.0001;  
   Data data &#61; new Data();  
   ArrayList> routes &#61; new ArrayList<>();  
   ArrayList> servetimes &#61; new ArrayList<>();  public Check(BaB_Vrptw lp) {  super();  this.data &#61; lp.data;  this.routes &#61; lp.routes;  this.servetimes &#61; lp.servetimes;  
   }  //函数功能&#xff1a;比较两个数的大小  public int double_compare(double v1,double v2) {  if (v1        }  if (v1 > v2 &#43; epsilon) {  return 1;  
       }  return 0;  
   }  //函数功能&#xff1a;解的可行性判断  public void fesible() throws IloException {  //车辆数量可行性判断  if (routes.size() > data.veh_num) {  
           System.out.println("error: vecnum!!!");  
           System.exit(0);  
       }  //车辆载荷可行性判断  for (int k &#61; 0; k            ArrayList route &#61; routes.get(k);  double capasity &#61; 0;  for (int i &#61; 0; i                capasity &#43;&#61; data.demands[route.get(i)];  
           }  if (capasity > data.cap) {  
               System.out.println("error: cap!!!");  
               System.exit(0);  
           }  
       }  //时间窗、车容量可行性判断  for (int k &#61; 0; k            ArrayList route &#61; routes.get(k);  
           ArrayList servertime &#61; servetimes.get(k);  double capasity &#61; 0;  for (int i &#61; 0; i  data.b[origin]) {  
                   System.out.println("error: servertime!");  
                   System.exit(0);  
               }  if (double_compare(si &#43; data.dist[origin][destination],data.b[destination]) > 0) {  
                   System.out.println(origin &#43; ": [" &#43; data.a[origin]  
                           &#43; ","&#43;data.b[origin]&#43;"]"&#43; " "&#43; si);  
                   System.out.println(destination &#43; ": [" &#43;  
                           data.a[destination] &#43; ","&#43;data.b[destination]&#43;"]"&#43; " "&#43; sj);  
                   System.out.println(data.dist[origin][destination]);  
                   System.out.println(destination &#43; ":" );  
                   System.out.println("error: forward servertime!");  
                   System.exit(0);  
               }  if (double_compare(sj - data.dist[origin][destination],data.a[origin]) <0) {  
                   System.out.println(origin &#43; ": [" &#43; data.a[origin]  
                           &#43; ","&#43;data.b[origin]&#43;"]"&#43; " "&#43; si);  
                   System.out.println(destination &#43; ": [" &#43; data.a[destination]  
                           &#43; ","&#43;data.b[destination]&#43;"]"&#43; " "&#43; sj);  
                   System.out.println(data.dist[origin][destination]);  
                   System.out.println(destination &#43; ":" );  
                   System.out.println("error: backward servertime!");  
                   System.exit(0);  
               }  
           }  if (capasity > data.cap) {  
               System.out.println("error: cap!!!");  
               System.exit(0);  
           }  
       }  
   }  
}  

运算结果

以Solomon测试算例为例&#xff0c;我们对其进行了测试&#xff0c;输入数据如下&#xff1a;

27aa446d14487d81be42b646782a314b.png

输出结果如下&#xff1a;其中第一列代表顾客编号&#xff0c;第二列和第三列分别代表顾客的横纵坐标&#xff0c;第四列代表需求&#xff0c;第五列第六列代表时间窗&#xff0c;第七列代表服务时间。车辆数量20&#xff0c;容量200。

time 25.245537525 bestcost 827.3

0 13 17 18 19 15 16 14 12 101

0 43 42 41 40 44 46 45 48 51 50 52 49 47 101

0 5 3 7 8 10 11 9 6 4 2 1 75 101

0 67 65 63 62 74 72 61 64 68 66 69 101

0 20 24 25 27 29 30 28 26 23 22 21 101

0 32 33 31 35 37 38 39 36 34 101

0 57 55 54 53 56 58 60 59 101

0 81 78 76 71 70 73 77 79 80 101

0 90 87 86 83 82 84 85 88 89 91 101

0 98 96 95 94 92 93 97 100 99 101

第一行是运算时间和最优目标值&#xff0c;第二行到最后一行分别表示每辆车的运行路线。

终于搞完了&#xff0c;是不是觉得很舒爽呢&#xff1f;

欲下载代码请移步留言区。




推荐阅读
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • 本文介绍了如何通过维持两个堆来获取一个数据流中的中位数。通过使用最大堆和最小堆,分别保存数据流中较小的一半和较大的一半数值,可以保证两个堆的大小差距为1或0。如果数据流中的数量为奇数,则中位数为较大堆的最大值;如果数量为偶数,则中位数为较大堆的最大值和较小堆的最小值的平均值。可以使用优先队列来实现堆的功能。本文还提供了相应的Java代码实现。 ... [详细]
  • C++ STL复习(13)容器适配器
    STL提供了3种容器适配器,分别为stack栈适配器、queue队列适配器以及priority_queue优先权队列适配器。不同场景下,由于不同的序列式 ... [详细]
  • RingBuffer,或者说CircularBuffer,是一个长度固定的缓冲区,当从一端插入元素超过指定的最大长度时,缓冲区另一端的元素 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • MySQL数据库锁机制及其应用(数据库锁的概念)
    本文介绍了MySQL数据库锁机制及其应用。数据库锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决的问题。MySQL的锁机制相对简单,不同的存储引擎支持不同的锁机制,主要包括表级锁、行级锁和页面锁。本文详细介绍了MySQL表级锁的锁模式和特点,以及行级锁和页面锁的特点和应用场景。同时还讨论了锁冲突对数据库并发访问性能的影响。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 广度优先遍历(BFS)算法的概述、代码实现和应用
    本文介绍了广度优先遍历(BFS)算法的概述、邻接矩阵和邻接表的代码实现,并讨论了BFS在求解最短路径或最短步数问题上的应用。以LeetCode中的934.最短的桥为例,详细阐述了BFS的具体思路和代码实现。最后,推荐了一些相关的BFS算法题目供大家练习。 ... [详细]
  • 初识java关于JDK、JRE、JVM 了解一下 ... [详细]
  • java线程池的实现原理源码分析
    这篇文章主要介绍“java线程池的实现原理源码分析”,在日常操作中,相信很多人在java线程池的实现原理源码分析问题上存在疑惑,小编查阅了各式资 ... [详细]
author-avatar
大约在冬季1122_867
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有