前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >模拟退火算法解决带时间窗的车辆路径规划问题

模拟退火算法解决带时间窗的车辆路径规划问题

作者头像
用户1621951
发布2021-09-02 11:42:45
2K1
发布2021-09-02 11:42:45
举报
文章被收录于专栏:数据魔术师数据魔术师

各位读者大家好,今天小编将给大家分享如何用模拟推退火算法解决带时间窗的车辆路径规划问题。本文附带Java代码详解,是根据过去学长写的用禁忌搜索算法求解相关问题的代码修改而来的:

禁忌搜索算法求解带时间窗的车辆路径规划问题详解(附Java代码)

问题描述

车辆路径规划问题(VRP)是运筹学中经典,它是指对一系列发货点和收货点,组织调用一定的车辆,安排适当的行车路线,使车辆有序地通过它们,在满足指定的约束条件下(例如:车辆容量限制,行驶时间限制等),力争实现一定的目标。

带时间窗的车辆路径规划问题(Vehicle Routing Problem with Time Window,VRPTW)是在VRP基础上添加配送时间约束条件产生的一个新问题。在这类问题中,给定车辆到达目的地的最早时间和最晚时间,要求车辆必须在规定的时间窗内到达,这是一个硬性条件,但是在搜索过程中却可以适当无视此条件以扩大搜索范围。此时,决策如何规划调度车辆使得配送的总费用最小化。

VRPTW的更多详细介绍可以参考之前的推文:

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

算法介绍

模拟退火算法是启发式算法的一种,也是一种贪心算法,它从某一较高初温出发,伴随温度参数的不断下降,结合概率突跳特性在解空间中随机寻找目标函数的全局最优解,即在局部最优解能概率性地跳出并最终趋于全局最优。

模拟退火算法的更多详细介绍可以参考之前的推文:

干货 | 用模拟退火(SA, Simulated Annealing)算法解决旅行商问题

01

#评价函数介绍#

需要注意的是,评价函数的作用并不只是评价该解是否为更优解,在每次调用评价函数的过程中,都会动态改变系数a,b

若目标函数值符合对容量、时间窗的要求,则惩罚系数a,b降低,算法对不符合要求的解的容忍度提高;反之,惩罚系数a,b提高,算法对不符合要求的解的容忍度降低。

在这样的动态调整下,算法能同时朝着“满足容量、时间的要求”和“追求总距离更短”的两大目标前进。

02

# 算子介绍 #

本文用的是插入算子,即随机选择一个顾客节点a,遍历除该节点所在路径外其他路径的每个位置,依次将该节点a插入每一个位置,算出此时是否为更优解,待全部遍历一遍后,将该节点a插入最优位置。

例如,下面有三条路径,1号节点为所有车辆的出发点和收货点:

此时所有车辆的总距离约为248。

随机选择出一个节点13,将它插入2、3路径的每一个位置,看是否能得到更优解。最终发现将其插入路径3结果更优,则结果如下:

将节点13从路径1移除并插入路径3,结果约为193,明显比原解更优。

代码如下:

代码语言:javascript
复制
 public static double CreateNew() {//relocation插入算子
  InitAndPrint read=new InitAndPrint();
  int position= 0,rand=0,path=0;
  int bestRoute = 0;// best route 所属路径
  int bestPosition = 0; // best position所在位置
  double Ans = INF,temp1,temp2;// best vehicle 所属车辆
  double score = 0;
  while(rand<=1||rand>CustomerNumber+1)
   rand= (int)(Math.random()*CustomerNumber)+1;
  for(int i=1;i< VehicleNumber;i++) 
   for(int j=0;j<routes[i].seq.size();j++) 
    if(routes[i].seq.get(j).Number==rand) {
     path=i;
     position = j;// 标记位置
     break;
    }
  read.Output(routes);
  System.out.print("a:"+path+" "+customers[rand].Path_Number+" "+position+" "+rand+"  ");
  removenode(path, position, rand);// 将客户i从原路径的第P个位置中移除
  
  for (int j = 1; j <= VehicleNumber;j++)
   for (int l = 1; l < routes[j].seq.size(); l++)// 分别枚举每一个节点所在位置
    if (customers[rand].Path_Number != j) {
     addnode(j, l, rand);// 将客户l插入路径j的第i个位置
     temp1 = routes[customers[rand].Path_Number].SubT; // 记录原先所在路径的时间窗违反总和
     temp2 = routes[j].SubT; // 记录插入的路径时间窗违反总和,这样就不用再update一次了
     // 更新i节点移出的路径:
     routes[customers[rand].Path_Number].SubT = 0;
     UpdateSubT(routes[customers[rand].Path_Number]);
     // 更新i节点移入的路径j:
     routes[j].SubT = 0;
     UpdateSubT(routes[j]);
     score = Calculation(routes);// 计算目标函数值
     if(score<Ans) {
      Ans =score; // best vehicle 所属车辆
      System.out.print(j);
      bestRoute = j; // best route 所属路径
      bestPosition = l; // best position所在位置
     }
     routes[customers[rand].Path_Number].SubT = temp1;
     routes[j].SubT = temp2;
     removenode(j, l, rand);
    }
  addnode(customers[rand].Path_Number, position, rand);//与前面的第一次remove呼应,复原
  //正式插入
  for (int i = 1; i < routes[customers[rand].Path_Number].seq.size(); i++)
   if (routes[customers[rand].Path_Number].seq.get(i).Number == rand) {
    position= i;
    break;
   }
  removenode(customers[rand].Path_Number, position,rand);
  addnode(bestRoute, bestPosition, rand);
  routes[bestRoute].SubT = 0;
  UpdateSubT(routes[bestRoute]);
  routes[customers[rand].Path_Number].SubT = 0;
  UpdateSubT(routes[customers[rand].Path_Number]);
  customers[rand].Path_Number= bestRoute;
  return Ans;
 }
  
   public static void addnode(int r, int position, int cus) {// 节点加入的路径routes[r],节点customer[Cus],节点加入路径的位置pos
  // 更新在路径r中加上节点Cus的需求
  routes[r].Load += customers[cus].Demand;
  // 更新在路径r中插入节点Cus后所组成的路径距离
  routes[r].Dis = routes[r].Dis
    - Graph[routes[r].seq.get(position - 1).Number][routes[r].seq.get(position).Number]
    + Graph[routes[r].seq.get(position - 1).Number][customers[cus].Number]
    + Graph[routes[r].seq.get(position).Number][customers[cus].Number];
  // 在路径r中插入节点Cus
  routes[r].seq.add(position, new CustomerType(customers[cus]));
 }
  
   public static void removenode(int r, int position, int Cus) {// 节点去除的路径routes[r],节点customer[cus],节点所在路径的位置pos
  // 更新在路径r中去除节点Cus的需求
  routes[r].Load -= customers[Cus].Demand;
  // 更新在路径r中去除节点Cus后所组成的路径的距离
  routes[r].Dis = routes[r].Dis
    - Graph[routes[r].seq.get(position - 1).Number][routes[r].seq.get(position).Number]
    - Graph[routes[r].seq.get(position).Number][routes[r].seq.get(position + 1).Number]
    + Graph[routes[r].seq.get(position - 1).Number][routes[r].seq.get(position + 1).Number];
  // 从路径r中去除节点Cus
  routes[r].seq.remove(position);
 }

03

# 模拟退火算法筛选较优解 #

大体的思路如下:

1) 利用插入算法产生一个新解P(i+1),计算该解的目标函数值L( P(i+1) )。

2) 若L(P(i+1)) < L(P(i)),则接受P(i+1)为新的路径,否则以模拟退火的一定概率接受P(i+1) ,然后降温。

3) 重复步骤1,2直到满足退出条件。

代码如下:

代码语言:javascript
复制
 public static void Search() {
  while (temperature > 1) {
   double currentScore = Calculation(currentroutes);
   double bestScore = Calculation(bestroutes);
   double score = CreateNew();
   if (score < currentScore) {// 接受
    currentScore = score;
    for (int i = 1; i <= VehicleNumber; i++)
     deepcopy(currentroutes[i], routes[i]);
    if (Check(routes) == true && bestScore > currentScore) {
     bestScore = currentScore;
     for (int i = 1; i <= VehicleNumber; i++)
      deepcopy(bestroutes[i], currentroutes[i]);
    }
   } else {
    if (Math.random() < Math.exp(-(score - currentScore) / temperature)) {// 接受
     currentScore = score;
     for (int i = 1; i <= VehicleNumber; i++)
      deepcopy(currentroutes[i], routes[i]);
    } else
     for (int i = 1; i <= VehicleNumber; i++)
      deepcopy(routes[i], currentroutes[i]);// 还原
    if (Check(routes) == true && bestScore > currentScore) {
     bestScore = currentScore;
     for (int i = 1; i <= VehicleNumber; i++)
      deepcopy(bestroutes[i], currentroutes[i]);
    }
   }
   temperature = speed * temperature;
  }
 }

运行结果展示

本算例得到的较好解总距离约为191,且结果符合对时间及容量要求。其结果与用禁忌搜索算法计算相同算例得到的结果一致

特别提醒:模拟退火算法是启发式算法,结果受编程语言、实现过程、参数设置等因素的影响。感兴趣的小伙伴也可以自己尝试一下哟。

- END -

文案&排版:王思涵(华中科技大学管理学院本科一年级)

指导老师:秦虎老师(华中科技大学管理学院)

审稿:周航(华中科技大学管理学院本科二年级)

如对文中内容有疑问,欢迎交流。PS:部分资料来自网络。


如有需求,可以联系:

秦虎老师(华中科技大学管理学院:professor.qin@qq.com)

王思涵(华中科技大学管理学院本科一年级:2278159431@qq.com)

周航(华中科技大学管理学院本科二年级:zh20010728@126.com)

欢迎大家加入数据魔术师粉丝群,我们的活动将会通过粉丝群优先发布, 学习资料将通过粉丝群分享。

欲入群,请转发此文,然后扫描下方二维码联系数据魔术师小助手

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据魔术师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档