【进阶五】Python实现SDVRP(需求拆分)常见求解算法——禁忌搜索+模拟退火算法(TS+SA)

本文介绍了如何使用Python结合禁忌搜索(TS)和模拟退火(SA)算法解决需求拆分车辆路线规划问题(SDVRP),详细阐述了先验拆分策略的应用以及代码调整,包括数据结构、距离矩阵计算和邻域搜索操作。
摘要由CSDN通过智能技术生成

基于python语言,采用经典禁忌搜索(TS)+模拟退火(SA)对 需求拆分车辆路径规划问题(SDVRP) 进行求解。

往期优质资源


经过一年多的创作,目前已经成熟的代码列举如下,如有需求可私信联系,表明需要的 问题与算法,原创不宜,有偿获取。
VRP问题GAACOALNSDEDPSOQDPSOTSSA
CVRP
VRPTW
MDVRP
MDHVRP
MDHVRPTW
SDVRP

1. 适用场景

  • 求解CVRP
  • 车辆类型单一
  • 车辆容量小于部分需求节点需求
  • 单一车辆基地

2. 代码调整


CVRP问题相比,SDVRP问题允许客户需求大于车辆容量。为了使得每个客户的需求得到满足,必须派遣一辆或多辆车辆对客户进行服务,也就是需要对客户的需求进行拆分。关于如何进行拆分一般有两种方式:

  • 先验拆分策略:提前制定策略对客户的需求(尤其是大于车辆容量的客户需求)进行分解,将SDVRP问题转化为CVRP问题
  • 过程拆分策略:在车辆服务过程中对客户需求进行动态拆分

本文采用文献[1]提出的先验分割策略,表述如下:

(1)20/10/5/1拆分规则

  • m20 =max{ m ∈ Z + ∪ { 0 } ∣ 0.20 Q m < = D i m\in Z^+ \cup \{0\} | 0.20Qm <= D_i mZ+{0}∣0.20Qm<=Di }
  • m10 =max{ m ∈ Z + ∪ { 0 } ∣ 0.10 Q m < = D i − 0.20 Q m 20   m\in Z^+ \cup \{0\} | 0.10Qm <= D_i-0.20Qm_{20}~ mZ+{0}∣0.10Qm<=Di0.20Qm20  }
  • m5 =max{ m ∈ Z + ∪ { 0 } ∣ 0.05 Q m < = D i − 0.20 Q m 20 − 0.10 Q m 10 m\in Z^+ \cup \{0\} | 0.05Qm <= D_i-0.20Qm_{20}-0.10Qm_{10} mZ+{0}∣0.05Qm<=Di0.20Qm200.10Qm10 }
  • m1 =max{ m ∈ Z + ∪ { 0 } ∣ 0.01 Q m < = D i − 0.20 Q m 20 − 0.10 Q m 10 − 0.05 Q m 5 m\in Z^+ \cup \{0\} | 0.01Qm <= D_i-0.20Qm_{20}-0.10Qm_{10}-0.05Qm_{5} mZ+{0}∣0.01Qm<=Di0.20Qm200.10Qm100.05Qm5 }

(2)25/10/5/1拆分规则

  • m25 =max{ m ∈ Z + ∪ { 0 } ∣ 0.25 Q m < = D i m\in Z^+ \cup \{0\} | 0.25Qm <= D_i mZ+{0}∣0.25Qm<=Di }
  • m10 =max{ m ∈ Z + ∪ { 0 } ∣ 0.10 Q m < = D i − 0.25 Q m 25   m\in Z^+ \cup \{0\} | 0.10Qm <= D_i-0.25Qm_{25}~ mZ+{0}∣0.10Qm<=Di0.25Qm25  }
  • m5 =max{ m ∈ Z + ∪ { 0 } ∣ 0.05 Q m < = D i − 0.25 Q m 25 − 0.10 Q m 10 m\in Z^+ \cup \{0\} | 0.05Qm <= D_i-0.25Qm_{25}-0.10Qm_{10} mZ+{0}∣0.05Qm<=Di0.25Qm250.10Qm10 }
  • m1 =max{ m ∈ Z + ∪ { 0 } ∣ 0.01 Q m < = D i − 0.25 Q m 25 − 0.10 Q m 10 − 0.05 Q m 5 m\in Z^+ \cup \{0\} | 0.01Qm <= D_i-0.25Qm_{25}-0.10Qm_{10}-0.05Qm_{5} mZ+{0}∣0.01Qm<=Di0.25Qm250.10Qm100.05Qm5 }

在实现过程中,对于需求超过车辆容量的客户必须进行需求拆分,而对于未超过车辆容量的客户可以拆分也可以不拆分,这里设置了参数比例进行限制。

3. 求解结果


3.1 TS


(1)收敛曲线
在这里插入图片描述

(2)车辆路径
在这里插入图片描述

3.2 SA


(1)收敛曲线

在这里插入图片描述

(2)车辆路径
在这里插入图片描述


4. 代码片段


(1)数据结构

# 数据结构:解
class Sol():
    def __init__(self):
        self.node_no_seq = None # 节点id有序排列
        self.obj = None # 目标函数
        self.fitness = None  # 适应度
        self.route_list = None # 车辆路径集合
        self.route_distance_list = None  # 车辆路径长度集合
        self.action_id = None # 对应的算子id
# 数据结构:网络节点
class Node():
    def __init__(self):
        self.id = 0 # 节点id
        self.x_coord = 0 # 节点平面横坐标
        self.y_coord = 0 # 节点平面纵坐标
        self.demand = 0 # 节点需求
# 数据结构:全局参数
class Model():
    def __init__(self):
        self.best_sol = None # 全局最优解
        self.demand_id_list = [] # 需求节点集合
        self.demand_dict = {}
        self.sol_list = [] # 解的集合
        self.depot = None # 车场节点
        self.number_of_demands = 0 # 需求节点数量
        self.vehicle_cap = 0 # 车辆最大容量
        self.distance_matrix = {} # 节点距离矩阵
        self.demand_id_list_ = [] # 经先验需求分割后的节点集合
        self.demand_dict_ = {} # 需求分割后的节点需求集合
        self.distance_matrix_ = {}  # 原始节点id间的距离矩阵
        self.mapping = {}  # 需求分割前后的节点对应关系
        self.split_rate = 0.5 # 控制需求分割的比例(需求超出车辆容量的除外)
        self.popsize = 100 # 种群规模
        self.tabu_list=None # 禁忌表
        self.TL=30 # 禁忌长度

(2)距离矩阵

# 初始化参数
def cal_distance_matrix(model):
    for i in model.demand_id_list:
        for j in model.demand_id_list:
            d=math.sqrt((model.demand_dict[i].x_coord-model.demand_dict[j].x_coord)**2+
                        (model.demand_dict[i].y_coord-model.demand_dict[j].y_coord)**2)
            model.distance_matrix[i,j]=max(d,0.0001) if i != j else d
        dist = math.sqrt((model.demand_dict[i].x_coord - model.depot.x_coord) ** 2 + (model.demand_dict[i].y_coord - model.depot.y_coord) ** 2)
        model.distance_matrix[i, model.depot.id] = dist
        model.distance_matrix[model.depot.id, i] = dist

(3)邻域

# 定义邻域算子
def createActions(n):
    action_list=[]
    nswap=n//2
    #第一种算子(Swap):前半段与后半段对应位置一对一交换
    for i in range(nswap):
        action_list.append([1,i,i+nswap])
    #第二中算子(DSwap):前半段与后半段对应位置二对二交换
    for i in range(0,nswap-1,2):
        action_list.append([2,i,i+nswap])
    #第三种算子(Reverse):指定长度的序列反序
    for i in range(0,n,4):
        action_list.append([3,i,i+3])
    return action_list
# 执行邻域搜索
def doAction(node_no_seq,action):
    node_no_seq=copy.deepcopy(node_no_seq)
    if action[0]==1:
        index_1=action[1]
        index_2=action[2]
        temporary=node_no_seq[index_1]
        node_no_seq[index_1]=node_no_seq[index_2]
        node_no_seq[index_2]=temporary
        return node_no_seq
    elif action[0]==2:
        index_1 = action[1]
        index_2 = action[2]
        temporary=[node_no_seq[index_1],node_no_seq[index_1+1]]
        node_no_seq[index_1]=node_no_seq[index_2]
        node_no_seq[index_1+1]=node_no_seq[index_2+1]
        node_no_seq[index_2]=temporary[0]
        node_no_seq[index_2+1]=temporary[1]
        return node_no_seq
    elif action[0]==3:
        index_1=action[1]
        index_2=action[2]
        node_no_seq[index_1:index_2+1]=list(reversed(node_no_seq[index_1:index_2+1]))
        return node_no_seq

参考

【1】 A novel approach to solve the split delivery vehicle routing problem

禁忌搜索算法Python实现可以参考以下步骤: 1. 初始化:在搜索空间中随机生成一个初始解i,并将禁忌表H置空。同时,将当前解i设为历史最优解s。 2. 迭代搜索:在每一次迭代中,根据禁忌表和邻域搜索策略生成候选解集合C,然后选择其中的一个候选解作为下一次搜索的当前解。同时,更新禁忌表。 3. 终止条件:设置一个终止条件,例如达到最大迭代次数或找到满意的解。 4. 输出结果:输出最优解和对应的最小值。 下面是一个简单的在Python实现禁忌搜索算法的示例代码: ```python class TabuSearch: def __init__(self, iters): self.iters = iters # 初始化其他参数 def generate_initial_solution(self): # 生成初始解i pass def generate_candidates(self, current_solution): # 根据禁忌表和邻域搜索策略生成候选解集合C pass def update_tabu_list(self, tabu_list, candidate): # 更新禁忌表 pass def valuate(self, solution): # 计算解的价值 pass def run(self): current_solution = self.generate_initial_solution() best_solution = current_solution for iter in range(self.iters): candidates = self.generate_candidates(current_solution) next_solution = self.select_next_solution(candidates) self.update_tabu_list(tabu_list, next_solution) if self.valuate(next_solution) < self.valuate(best_solution): best_solution = next_solution return best_solution def main(): ts = TabuSearch(iters=200) best_solution = ts.run() print('最优解:', best_solution) print('最小值:', ts.valuate(best_solution)) plt.plot(ts.trace, 'r') title = 'TS: ' + str(ts.valuate(best_solution)) plt.title(title) plt.show() if __name__ == "__main__": main() ``` 需要注意的是,这只是一个简化的示例代码,实际中可能还需要根据具体问题进行相应的调整和修改。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [禁忌搜索算法Tabu Search代码复现【Python】](https://download.csdn.net/download/qq_44186838/62601760)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [禁忌搜索算法(Tabu search)python实现](https://blog.csdn.net/weixin_39124421/article/details/85158330)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Better.C

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值