Python实现VRP常见求解算法——自适应大邻域搜索算法(ALNS)

自适应大邻域搜索代码系列之(1) - 使用ALNS代码框架求解TSP问题 前言 上次出了邻域搜索的各种概念科普,尤其是LNS和ALNS的具体过程更是描述得一清二楚。不知道你萌都懂了吗?小编相信大家早就get到啦。不过有个别不愿意透露姓名的热心网友表示上次没有代码,遂不过瘾啊~哎,大家先别急,代码有得你们酸爽的…… 不过由于ALNS的代码量实在太大,小编打算把这个做成一个系列来一一为大家讲解,好让小伙伴们彻底把这个算法框架的代码吃透。今天暂时还是先不对代码进行讲解,先来教... 阅读详情
AI摘要

本文介绍使用自适应大邻域搜索(ALNS)算法解决车辆路径规划问题(CVRP),并详细阐述了该算法的实现过程,包括数据结构定义、参数初始化、初始解生成等步骤。

基于python语言,实现经典自适应大邻域搜索算法(ALNS)对车辆路径规划问题(CVRP)进行求解。

优质资源

1. 适用场景

  • 求解CVRP
  • 车辆类型单一
  • 车辆容量不小于需求节点最大需求
  • 单一车辆基地

2. 求解效果

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

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

3. 问题分析

CVRP问题的解为一组满足需求节点需求的多个车辆的路径集合。假设某物理网络中共有10个顾客节点,编号为1~10,一个车辆基地,编号为0,在满足车辆容量约束与顾客节点需求约束的条件下,此问题的一个可行解可表示为:[0-1-2-0,0-3-4-5-0,0-6-7-8-0,0-9-10-0],即需要4个车辆来提供服务,车辆的行驶路线分别为0-1-2-0,0-3-4-5-0,0-6-7-8-0,0-9-10-0。由于车辆的容量固定,基地固定,因此可以将上述问题的解先表示为[1-2-3-4-5-6-7-8-9-10]的有序序列,然后根据车辆的容量约束,对序列进行切割得到若干车辆的行驶路线。因此可以将CVRP问题转换为TSP问题进行求解,得到TSP问题的优化解后再考虑车辆容量约束进行路径切割,得到CVRP问题的解。这样的处理方式可能会影响CVRP问题解的质量,但简化了问题的求解难度。

4. 数据格式

以xlsx文件储存网络数据,其中第一行为标题栏,第二行存放车辆基地数据。在程序中车辆基地seq_no编号为-1,需求节点seq_id从0开始编号。可参考github主页相关文件。

5. 分步实现

(1)数据结构
为便于数据处理,定义Sol()类,Node()类,Model()类,其属性如下表:

  • Sol()类,表示一个可行解
属性描述
nodes_seq需求节点seq_no有序排列集合,对应TSP的解
obj优化目标值
routes车辆路径集合,对应CVRP的解
  • Node()类,表示一个网络节点
属性描述
id物理节点id,可选
name物理节点名称,可选
seq_no物理节点映射id,基地节点为-1,需求节点从0编号
x_coord物理节点x坐标
y_coord物理节点y坐标
demand物理节点需求
  • Model()类,存储算法参数
属性描述
best_sol全局最优解,值类型为Sol()
node_list物理节点集合,值类型为Node()
node_seq_no_list物理节点映射id集合
depot车辆基地,值类型为Node()
number_of_nodes需求节点数量
opt_type优化目标类型,0:最小车辆数,1:最小行驶距离
vehicle_cap车辆容量
distance网络弧距离
rand_d_max随机破坏程度上限
rand_d_min随机破坏程度下限
worst_d_max最坏破坏程度上限
worst_d_min最坏破坏程度下限
regret_n次优位置个数
r1算子奖励1
r2算子奖励2
r3算子奖励3
rho算子权重衰减系数
d_weight破坏算子权重
d_select破坏算子被选中次数/每轮
d_score破坏算子被奖励得分/每轮
d_history_select破坏算子历史共计被选中次数
d_history_score破坏算子历史共计被奖励得分
r_weight修复算子权重
r_select修复算子被选中次数/每轮
r_score修复算子被奖励得分/每轮
r_history_select修复算子历史共计被选中次数
r_history_score修复算子历史共计被奖励得分
(2)文件读取
def readXlsxFile(filepath,model):
    # It is recommended that the vehicle depot data be placed in the first line of xlsx file
    node_seq_no = -1#the depot node seq_no is -1,and demand node seq_no is 0,1,2,...
    df = pd.read_excel(filepath)
    for i in range(df.shape[0]):
        node=Node()
        node.id=node_seq_no
        node.seq_no=node_seq_no
        node.x_coord= df['x_coord'][i]
        node.y_coord= df['y_coord'][i]
        node.demand=df['demand'][i]
        if df['demand'][i] == 0:
            model.depot=node
        else:
            model.node_list.append(node)
            model.node_seq_no_list.append(node_seq_no)
        try:
            node.name=df['name'][i]
        except:
            pass
        try:
            node.id=df['id'][i]
        except:
            pass
        node_seq_no=node_seq_no+1
    model.number_of_nodes=len(model.node_list)

(3)初始化参数
在初始化参数时计算网络弧距离,以便后续算子使用。

def initParam(model):
    for i in range(model.number_of_nodes):
        for j in range(i+1,model.number_of_nodes):
            d=math.sqrt((model.node_list[i].x_coord-model.node_list[j].x_coord)**2+
                        (model.node_list[i].y_coord-model.node_list[j].y_coord)**2)
            model.distance[i,j]=d
            model.distance[j,i]=d

(4)初始解

def genInitialSol(node_seq):
    node_seq=copy.deepcopy(node_seq)
    random.seed(0)
    random.shuffle(node_seq)
    return node_seq

(5)目标值计算
目标值计算依赖 " splitRoutes " 函数对TSP可行解分割得到车辆行驶路线和所需车辆数, " calDistance " 函数计算行驶距离。

def splitRoutes(nodes_seq,model):
    num_vehicle = 0
    vehicle_routes = []
    route = []
    remained_cap = model.vehicle_cap
    for node_no in nodes_seq:
        if remained_cap - model.node_list[node_no].demand >= 0:
            route.append(node_no)
            remained_cap = remained_cap - model.node_list[node_no].demand
        else:
            vehicle_routes.append(route)
            route = [node_no]
            num_vehicle = num_vehicle + 1
            remained_cap =model.vehicle_cap - model.node_list[node_no].demand
    vehicle_routes.append(route)
    return num_vehicle,vehicle_routes
def calDistance(route,model):
    distance=0
    depot=model.depot
    for i in range(len(route)-1):
        distance+=model.distance[route[i],route[i+1]]
    first_node=model.node_list[route[0]]
    last_node=model.node_list[route[-1]]
    distance+=math.sqrt((depot.x_coord-first_node.x_coord)**2+(depot.y_coord-first_node.y_coord)**2)
    distance+=math.sqrt((depot.x_coord-last_node.x_coord)**2+(depot.y_coord - last_node.y_coord)**2)
    return distance
def calObj(nodes_seq,model):
    num_vehicle, vehicle_routes = splitRoutes(nodes_seq, model)
    if model.opt_type==0:
        return num_vehicle,vehicle_routes
    else:
        distance=0
        for route in vehicle_routes:
            distance+=calDistance(route,model)
        return distance,vehicle_routes

(6)定义destroy算子(破坏算子)
这里实现两种destory:

  • random destroy : 随机从当前解中移除一定比例的需求节点
  • worst destroy:从当前解中移除一定比例引起目标函数增幅较大的需求节点
# define random destory action
def createRandomDestory(model):
    d=random.uniform(model.rand_d_min,model.rand_d_max)
    reomve_list=random.sample(range(model.number_of_nodes),int(d*model.number_of_nodes))
    return reomve_list
# define worse destory action
def createWorseDestory(model,sol):
    deta_f=[]
    for node_no in sol.nodes_seq:
        nodes_seq_=copy.deepcopy(sol.nodes_seq)
        nodes_seq_.remove(node_no)
        obj,vehicle_routes=calObj(nodes_seq_,model)
        deta_f.append(sol.obj-obj)
    sorted_id = sorted(range(len(deta_f)), key=lambda k: deta_f[k], reverse=True)
    d=random.randint(model.worst_d_min,model.worst_d_max)
    remove_list=sorted_id[:d]
    return remove_list

(7)定义repair算子(修复算子)
这里实现三种repair:

  • random repair :将被移除的需求节点随机插入已分配节点序列中;
  • greedy repair :根据被移除的需求节点插入已分配节点序列中每个可能位置的目标函数增量大小,依次选择目标函数增量最小的需求节点与插入位置组合,直到所有被移除的需求节点都重新插入为止(可简单理解为,依次选择使目标函数增量最小的需求节点与其最优的插入位置);
    a r g m i n p ∈ P , i ∈ I P f ( s p , i ) argmin_{p\in P,i\in IP} f(s_{p,i}) argminp∈P,i∈IP​f(sp,i​)
    其中, P P P为未分配的需求节点集合, I P IP IP为可能插入的位置集合, f ( s p , i ) f(s_{p,i}) f(sp,i​)为将需求节点 p p p插入 s s s中第 i i i个位置时的目标函数值。
  • regret repair:计算被移除节点插回到已分配节点序列中n个次优位置时其目标函数值与最优位置的目标函数值的差之和,然后选择差之和最大的需求节点及其最优位置。(可简单理解为,优先选择n个次优位置与最优位置距离较远的需求节点及其最优位置。);
    a r g m a x p ∈ P { ∑ i = 2 n ( f ( s i ( p ) ) − f ( s 1 ( p ) ) ) } argmax_{p\in P}\left \{ {\textstyle \sum_{i=2}^{n}(f(s_{i}(p))-f(s_{1}(p)))} \right \} argmaxp∈P​{∑i=2n​(f(si​(p))−f(s1​(p)))}
    其中, P P P为未分配的需求节点集合, f ( s i ( p ) ) − f ( s 1 ( p ) ) f(s_{i}(p))-f(s_{1}(p)) f(si​(p))−f(s1​(p))为将需求节点 p p p插入 s s s中最优位置和第 i i i次优位置时目标函数差值。
def createRandomRepair(remove_list,model,sol):
    unassigned_nodes_seq=[]
    assigned_nodes_seq = []
    # remove node from current solution
    for i in range(model.number_of_nodes):
        if i in remove_list:
            unassigned_nodes_seq.append(sol.nodes_seq[i])
        else:
            assigned_nodes_seq.append(sol.nodes_seq[i])
    # insert
    for node_no in unassigned_nodes_seq:
        index=random.randint(0,len(assigned_nodes_seq)-1)
        assigned_nodes_seq.insert(index,node_no)
    new_sol=Sol()
    new_sol.nodes_seq=copy.deepcopy(assigned_nodes_seq)
    new_sol.obj,new_sol.routes=calObj(assigned_nodes_seq,model)
    return new_sol

def createGreedyRepair(remove_list,model,sol):
    unassigned_nodes_seq = []
    assigned_nodes_seq = []
    # remove node from current solution
    for i in range(model.number_of_nodes):
        if i in remove_list:
            unassigned_nodes_seq.append(sol.nodes_seq[i])
        else:
            assigned_nodes_seq.append(sol.nodes_seq[i])
    #insert
    while len(unassigned_nodes_seq)>0:
        insert_node_no,insert_index=findGreedyInsert(unassigned_nodes_seq,assigned_nodes_seq,model)
        assigned_nodes_seq.insert(insert_index,insert_node_no)
        unassigned_nodes_seq.remove(insert_node_no)
    new_sol=Sol()
    new_sol.nodes_seq=copy.deepcopy(assigned_nodes_seq)
    new_sol.obj,new_sol.routes=calObj(assigned_nodes_seq,model)
    return new_sol

def findGreedyInsert(unassigned_nodes_seq,assigned_nodes_seq,model):
    best_insert_node_no=None
    best_insert_index = None
    best_insert_cost = float('inf')
    assigned_nodes_seq_obj,_=calObj(assigned_nodes_seq,model)
    for node_no in unassigned_nodes_seq:
        for i in range(len(assigned_nodes_seq)):
            assigned_nodes_seq_ = copy.deepcopy(assigned_nodes_seq)
            assigned_nodes_seq_.insert(i, node_no)
            obj_, _ = calObj(assigned_nodes_seq_, model)
            deta_f = obj_ - assigned_nodes_seq_obj
            if deta_f<best_insert_cost:
                best_insert_index=i
                best_insert_node_no=node_no
                best_insert_cost=deta_f
    return best_insert_node_no,best_insert_index

def createRegretRepair(remove_list,model,sol):
    unassigned_nodes_seq = []
    assigned_nodes_seq = []
    # remove node from current solution
    for i in range(model.number_of_nodes):
        if i in remove_list:
            unassigned_nodes_seq.append(sol.nodes_seq[i])
        else:
            assigned_nodes_seq.append(sol.nodes_seq[i])
    # insert
    while len(unassigned_nodes_seq)>0:
        insert_node_no,insert_index=findRegretInsert(unassigned_nodes_seq,assigned_nodes_seq,model)
        assigned_nodes_seq.insert(insert_index,insert_node_no)
        unassigned_nodes_seq.remove(insert_node_no)
    new_sol = Sol()
    new_sol.nodes_seq = copy.deepcopy(assigned_nodes_seq)
    new_sol.obj, new_sol.routes = calObj(assigned_nodes_seq, model)
    return new_sol

def findRegretInsert(unassigned_nodes_seq,assigned_nodes_seq,model):
    opt_insert_node_no = None
    opt_insert_index = None
    opt_insert_cost = -float('inf')
    for node_no in unassigned_nodes_seq:
        n_insert_cost=np.zeros((len(assigned_nodes_seq),3))
        for i in range(len(assigned_nodes_seq)):
            assigned_nodes_seq_=copy.deepcopy(assigned_nodes_seq)
            assigned_nodes_seq_.insert(i,node_no)
            obj_,_=calObj(assigned_nodes_seq_,model)
            n_insert_cost[i,0]=node_no
            n_insert_cost[i,1]=i
            n_insert_cost[i,2]=obj_
        n_insert_cost= n_insert_cost[n_insert_cost[:, 2].argsort()]
        deta_f=0
        for i in range(1,model.regret_n):
            deta_f=deta_f+n_insert_cost[i,2]-n_insert_cost[0,2]
        if deta_f>opt_insert_cost:
            opt_insert_node_no = int(n_insert_cost[0, 0])
            opt_insert_index=int(n_insert_cost[0,1])
            opt_insert_cost=deta_f
    return opt_insert_node_no,opt_insert_index

(8)随机选择算子
采用轮盘赌法选择destory和repair算子。

#select destory action and repair action
def selectDestoryRepair(model):
    d_weight=model.d_weight
    d_cumsumprob = (d_weight / sum(d_weight)).cumsum()
    d_cumsumprob -= np.random.rand()
    destory_id= list(d_cumsumprob > 0).index(True)

    r_weight=model.r_weight
    r_cumsumprob = (r_weight / sum(r_weight)).cumsum()
    r_cumsumprob -= np.random.rand()
    repair_id = list(r_cumsumprob > 0).index(True)
    return destory_id,repair_id

(9)执行destory算子
根据被选择的destory算子返回需要被移除的节点index序列。

# do destory action
def doDestory(destory_id,model,sol):
    if destory_id==0:
        reomve_list=createRandomDestory(model)
    else:
        reomve_list=createWorseDestory(model,sol)
    return reomve_list

(10)执行repair算子
根据被选择的repair算子对当前接进行修复操作。

def doRepair(repair_id,reomve_list,model,sol):
    if repair_id==0:
        new_sol=createRandomRepair(reomve_list,model,sol)
    elif repair_id==1:
        new_sol=createGreedyRepair(reomve_list,model,sol)
    else:
        new_sol=createRegretRepair(reomve_list,model,sol)
    return new_sol

(11)重置算子得分
在每执行pu次destory和repair,重置算子的得分和被选中次数。

# reset action score
def resetScore(model):

    model.d_select = np.zeros(2)
    model.d_score = np.zeros(2)

    model.r_select = np.zeros(3)
    model.r_score = np.zeros(3)

(12)更新算子权重
对于算子权重的更新有两种策略,一种是每执行依次destory和repair更新一次,另一种是每执行pu次destory和repair更新一次权重。前者,能够保证权重及时得到更新,但却需要更多的计算时间;后者,通过合理设置pu参数,节省了计算时间,同时又不至于权重更新太滞后。这里采用后者更新策略。
w ( h ) = { ( 1 − ρ ) w ( h ) + ρ s ( h ) u ( h ) , i f u ( h ) > 0 ( 1 − ρ ) w ( h ) , i f u ( h ) = 0 w(h)=\left\{\begin{matrix} &(1-\rho)w(h)+\rho \frac{s(h)}{u(h)}, &if &u(h)>0 \\ & (1-\rho )w(h),&if & u(h)=0 \end{matrix}\right. w(h)={​(1−ρ)w(h)+ρu(h)s(h)​,(1−ρ)w(h),​ifif​u(h)>0u(h)=0​
其中, w ( h ) w(h) w(h)表示算子权重, ρ \rho ρ为衰减系数, s ( h ) s(h) s(h)为算子总奖励得分, u ( h ) u(h) u(h)为算子被选中次数。

# update action weight
def updateWeight(model):
    for i in range(model.d_weight.shape[0]):
        if model.d_select[i]>0:
            model.d_weight[i]=model.d_weight[i]*(1-model.rho)+model.rho*model.d_score[i]/model.d_select[i]
        else:
            model.d_weight[i] = model.d_weight[i] * (1 - model.rho)
    for i in range(model.r_weight.shape[0]):
        if model.r_select[i]>0:
            model.r_weight[i]=model.r_weight[i]*(1-model.rho)+model.rho*model.r_score[i]/model.r_select[i]
        else:
            model.r_weight[i] = model.r_weight[i] * (1 - model.rho)
    model.d_history_select = model.d_history_select + model.d_select
    model.d_history_score = model.d_history_score + model.d_score
    model.r_history_select = model.r_history_select + model.r_select
    model.r_history_score = model.r_history_score + model.r_score

(13)绘制收敛曲线

def plotObj(obj_list):
    plt.rcParams['font.sans-serif'] = ['SimHei']  #show chinese
    plt.rcParams['axes.unicode_minus'] = False   # Show minus sign
    plt.plot(np.arange(1,len(obj_list)+1),obj_list)
    plt.xlabel('Iterations')
    plt.ylabel('Obj Value')
    plt.grid()
    plt.xlim(1,len(obj_list)+1)
    plt.show()

(14)输出结果

def outPut(model):
    work=xlsxwriter.Workbook('result.xlsx')
    worksheet=work.add_worksheet()
    worksheet.write(0,0,'opt_type')
    worksheet.write(1,0,'obj')
    if model.opt_type==0:
        worksheet.write(0,1,'number of vehicles')
    else:
        worksheet.write(0, 1, 'drive distance of vehicles')
    worksheet.write(1,1,model.best_sol.obj)
    for row,route in enumerate(model.best_sol.routes):
        worksheet.write(row+2,0,'v'+str(row+1))
        r=[str(i)for i in route]
        worksheet.write(row+2,1, '-'.join(r))
    work.close()

(15)主函数
主函数中设置两层循环,外层循环由epochs参数控制,内层循环由pu参数控制,每执行一次内层循环更新一次算子权重。对于邻域新解的接受准则有很多类型,比如RW, GRE,SA,TA,OBA,GDA,这里采用TA准则,即f(newx)-f(x)<T时接受新解,T按照系数 φ衰减,初始T取为当前解*0.2。另外,在定义destory算子时,这里没有指定具体的破坏程度数值,而是采用一个区间,每次随机选择一个破坏程度对当前解进行破坏。更多算法细节可以参考文末资料1,关于加速技巧可以参考文末资料4,5。

def run(filepath,rand_d_max,rand_d_min,worst_d_min,worst_d_max,regret_n,r1,r2,r3,rho,phi,epochs,pu,v_cap,opt_type):
    """
    :param filepath: Xlsx file path
    :param rand_d_max: max degree of random destruction
    :param rand_d_min: min degree of random destruction
    :param worst_d_max: max degree of worst destruction
    :param worst_d_min: min degree of worst destruction
    :param regret_n:  n next cheapest insertions
    :param r1: score if the new solution is the best one found so far.
    :param r2: score if the new solution improves the current solution.
    :param r3: score if the new solution does not improve the current solution, but is accepted.
    :param rho: reaction factor of action weight
    :param phi: the reduction factor of threshold
    :param epochs: Iterations
    :param pu: the frequency of weight adjustment
    :param v_cap: Vehicle capacity
    :param opt_type: Optimization type:0:Minimize the number of vehicles,1:Minimize travel distance
    :return:
    """
    model=Model()
    model.rand_d_max=rand_d_max
    model.rand_d_min=rand_d_min
    model.worst_d_min=worst_d_min
    model.worst_d_max=worst_d_max
    model.regret_n=regret_n
    model.r1=r1
    model.r2=r2
    model.r3=r3
    model.rho=rho
    model.vehicle_cap=v_cap
    model.opt_type=opt_type
    readXlsxFile(filepath, model)
    initParam(model)
    history_best_obj = []
    sol = Sol()
    sol.nodes_seq = genInitialSol(model.node_seq_no_list)
    sol.obj, sol.routes = calObj(sol.nodes_seq, model)
    model.best_sol = copy.deepcopy(sol)
    history_best_obj.append(sol.obj)
    for ep in range(epochs):
        T=sol.obj*0.2
        resetScore(model)
        for k in range(pu):
            destory_id,repair_id=selectDestoryRepair(model)
            model.d_select[destory_id]+=1
            model.r_select[repair_id]+=1
            reomve_list=doDestory(destory_id,model,sol)
            new_sol=doRepair(repair_id,reomve_list,model,sol)
            if new_sol.obj<sol.obj:
                sol=copy.deepcopy(new_sol)
                if new_sol.obj<model.best_sol.obj:
                    model.best_sol=copy.deepcopy(new_sol)
                    model.d_score[destory_id]+=model.r1
                    model.r_score[repair_id]+=model.r1
                else:
                    model.d_score[destory_id]+=model.r2
                    model.r_score[repair_id]+=model.r2
            elif new_sol.obj-sol.obj<T:
                sol=copy.deepcopy(new_sol)
                model.d_score[destory_id] += model.r3
                model.r_score[repair_id] += model.r3
            T=T*phi
            print("%s/%s:%s/%s, best obj: %s" % (ep,epochs,k,pu, model.best_sol.obj))
            history_best_obj.append(model.best_sol.obj)
        updateWeight(model)

    plotObj(history_best_obj)
    outPut(model)
    print("random destory weight is {:.3f}\tselect is {}\tscore is {:.3f}".format(model.d_weight[0],
                                                                        model.d_history_select[0],
                                                                        model.d_history_score[0]))
    print("worse destory weight is {:.3f}\tselect is {}\tscore is {:.3f} ".format(model.d_weight[1],
                                                                        model.d_history_select[1],
                                                                        model.d_history_score[1]))
    print("random repair weight is {:.3f}\tselect is {}\tscore is {:.3f}".format(model.r_weight[0],
                                                                       model.r_history_select[0],
                                                                       model.r_history_score[0]))
    print("greedy repair weight is {:.3f}\tselect is {}\tscore is {:.3f}".format(model.r_weight[1],
                                                                       model.r_history_select[1],
                                                                       model.r_history_score[1]))
    print("regret repair weight is {:.3f}\tselect is {}\tscore is {:.3f}".format(model.r_weight[2],
                                                                       model.r_history_select[2],
                                                                       model.r_history_score[2]))

6. 完整代码

代码和数据文件可获取【私信】:

https://download.youkuaiyun.com/download/python_n/37357242

参考

  1. https://oparu.uni-ulm.de/xmlui/bitstream/handle/123456789/3264/vts_9568_14474.pdf?sequence=1
  2. https://homes.di.unimi.it/righini/Didattica/AlgoritmiEuristici/MaterialeAE/15%20-%20ALNS.pdf
  3. https://mp.weixin.qq.com/s/fpeNCKo62d_7gqBnWd2tTw
  4. https://mp.weixin.qq.com/s/l7fTHRye4pPBtWrlPrZ6mA
  5. https://mp.weixin.qq.com/s/K6EgtMdarCgr_vK4qZfGlw
ALNS算法(自适应大领域搜索算法) ALNS的C++实现 阅读详情

相关推荐

用Python手把手实现ALNS算法:从零搭建一个车辆路径规划(VRP)求解器

本文详细介绍了如何使用Python从零实现自适应大邻域搜索(ALNS)算法来构建车辆路径规划(VRP)求解器。通过完整的代码示例,讲解了ALNS的核心组件包括破坏-修复算子、自适应权重机制和模拟退火策略,并提供了性能优化技巧和实际应用建议,帮助开发者掌握这一强大的元启发式算法。

weixin_42523792的博客 451

【改进篇】Python实现VRP常见求解算法——自适应大邻域算法(ALNS)

基于python语言,实现经典自适应大邻域算法(ALNS)对车辆路径规划问题(CVRP)进行求解, 优化代码结构,改进Split函数

Better.C的博客 2717

Python实现VRP高效求解算法——自适应大邻域搜索(ALNS)的算子调优与性能分析

本文详细介绍了Python实现的自适应大邻域搜索算法(ALNS)在车辆路径问题(VRP)中的应用与优化。通过破坏算子和修复算子的动态组合,ALNS算法能够高效求解复杂物流配送问题。文章还提供了关键参数调优指南、性能优化技巧以及典型问题的解决方案,帮助开发者提升算法求解效率。

weixin_31421613的博客 328

【进阶二】Python实现VRPTW常见求解算法——自适应大邻域搜索算法(ALNS)

基于python语言,实现经典自适应大邻域搜索算法(ALNS)对带有时间窗的车辆路径规划问题( VRPTW )进行求解。

Better.C的博客 7452

用Python手把手实现ALNS算法:从零搭建一个车辆路径规划求解器(附完整代码)

本文详细介绍了如何使用Python从零实现自适应大邻域搜索(ALNS)算法来解决车辆路径规划问题。通过破坏/修复算子设计、权重更新机制和模拟退火接受准则的整合,构建了一个完整的ALNS求解器框架,并提供了可扩展的模块化代码实现。文章还包含实际应用示例和性能优化建议,帮助读者掌握这一高效的元启发式算法。

weixin_30689307的博客 448

【运筹优化】自适应大邻域搜索(ALNS)算法实战:从原理到代码实现与性能调优

本文深入解析自适应大邻域搜索(ALNS)算法,从核心原理到代码实现与性能调优。ALNS作为元启发式算法,通过智能化的破坏-重建策略解决组合优化问题,特别适用于车辆路径规划(VRP)等场景。文章详细介绍了破坏算子、修复算子的设计技巧,以及自适应权重更新机制,并提供了Python实现框架和实战案例,帮助开发者掌握这一高效运筹优化工具。

weixin_31364787的博客 272

5.Python实现VRP常见求解算法——自适应大邻域搜索算法(ALNS)

参考笔记 https://github.com/PariseC/Algorithms_for_solving_VRP 1. 适用场景 求解CVRP 车辆类型单一 车辆容量不小于需求节点最大需求 单一车辆基地 2. 问题分析 CVRP问题的解为一组满足需求节点需求的多个车辆的路径集合。 假设某物理网络中共有10个顾客节点,编号为1~10,一个车辆基地,编号为0,在满足车辆容量约束与顾客节点需求约束的条件下,此问题的一个可行解可表示为:[0-1-2-0,0-3-4-5-0,0-6-7-8-0,0-9-10-

qq_36744449的博客 3716

用Python搞定物流配送难题:手把手教你用ALNS算法求解带时间窗的VRP(附完整代码)

本文详细介绍了如何使用Python中的自适应大邻域搜索算法(ALNS)解决带时间窗的车辆路径问题(VRPTW)。通过破坏-修复双算子机制,ALNS能高效处理物流配送中的时间窗约束、车辆载重限制等复杂条件。文章包含完整代码实现、数据预处理方法、核心算法设计及电商配送案例解析,帮助开发者快速掌握这一物流优化利器。

weixin_28737603的博客 416

着实不错的自适应大邻域搜索算法ALNS

着实不错的自适应大邻域搜索算法ALNS

taozibaby的博客 3589

告别暴力穷举!用Python实现ALNS算法,5分钟搞定TSP旅行商问题(附完整代码)

本文详细介绍了如何使用Python实现自适应大邻域搜索(ALNS)算法,快速解决旅行商问题(TSP)。通过破坏-修复迭代循环和动态权重调整机制,ALNS在5分钟内即可求解中等规模TSP问题,显著优于传统暴力穷举法。文章包含完整代码实现、性能优化技巧及可视化示例,为运筹优化领域提供实用解决方案。

weixin_32642109的博客 377

告别调参玄学:用Python手把手实现ALNS的自适应算子权重调整

本文详细介绍了如何用Python实现自适应大规模邻域搜索算法(ALNS)的自适应算子权重调整机制。通过动态权重调整、得分系统和轮盘赌选择等核心组件,ALNS算法能够在车辆路径规划、生产调度等组合优化问题中实现智能优化。文章提供了完整的代码实现和调优经验,帮助开发者告别调参玄学,提升算法性能。

weixin_30751947的博客 385

ALNS-自适应大邻域搜索算法

是由Ropke与Pisinger在2006年提出的一种启发式方法,其在邻域搜索的基础上增加了对算子的作用效果的衡量,使算法能够自动选择好的算子对解进行破坏与修复,得到一个成本(cost)最低的一个解。由于算法的自动反馈更新权重机制,根据算子的历史表现与使用次数选择下一次迭代使用的算子,通过算子间的相互竞争来生成当前解的邻域结构,而在这种结构中有很大概率能够找到更好的解,同时可以尽可能地避免出现局部最优解的情况。

Lukkkka77的博客 4349

【CVRP】大邻域搜索算法求解带容量的车辆路径规划问题【含Matlab源码 2681期】

大邻域搜索算法求解带容量的车辆路径规划问题 完整的代码,方可运行;可提供运行操作视频!适合小白!

订阅付费专栏Matlab(奶茶价版),可赠送奶茶价版付费专栏指定代码1份; 376

【进阶六】Python实现SDVRPTW常见求解算法——自适应大邻域算法(ALNS)

基于python语言,采用经典自适应大邻域算法(ALNS)对 带硬时间窗的需求拆分车辆路径规划问题(SDVRPTW) 进行求解。

Better.C的博客 1718

卡尔曼滤波调参避坑指南:Q/R参数怎么设?实测5种传感器场景

本文针对卡尔曼滤波在工程实践中Q/R参数设置的难题,提供了一套实用的调参避坑指南。通过分析过程噪声方差Q与测量噪声方差R的物理意义与博弈关系,并结合温度传感器、陀螺仪、超声波测距等五种典型传感器场景的实测效果对比,总结出从噪声估算、离线仿真到收敛测试的系统性调参方法论,帮助工程师快速实现稳定有效的滤波效果。

weixin_29174141的博客 808

RK3588S开发板实战:手把手教你调试IMX415摄像头(附常见问题排查)

本文提供了基于RK3588S开发板调试IMX415摄像头的完整实战指南。从硬件连接到软件配置,详细解析了设备树(DTS)设置、驱动加载验证及V4L2工具链使用,并附带了常见问题排查方法,帮助开发者快速解决摄像头调试中的典型问题,实现图像采集与性能调优。

assembly8low的博客 598

【进阶五】Python实现SDVRP(需求拆分)常见求解算法——自适应大邻域算法(ALNS)

基于python语言,采用经典自适应大邻域算法(ALNS)对 需求拆分车辆路径规划问题(SDVRP) 进行求解。

Better.C的博客 1475

家庭路由器从入门到精通:连接设置、优化排障全指南

路由器作为家庭网络的核心枢纽,其工作原理基于NAT(网络地址转换)和DHCP(动态主机配置协议)两大关键技术。NAT负责将公网IP转换为多个内网IP,实现多设备共享上网;DHCP则自动为接入设备分配IP地址,简化网络配置。这些技术共同构建了稳定、安全的局域网环境,其价值在于为智能家居、在线办公、高清影音和游戏娱乐等场景提供可靠的网络基础。理解路由器的物理连接、后台设置与故障排查逻辑,是优化Wi-Fi信号覆盖、提升网络性能、保障家庭网络安全的关键。本文以路由器配置与排障为核心,详细解析从WAN/LAN口区分、

weixin_33393017的博客 406

如何快速掌握ALNS:Python自适应大邻域搜索算法的完整指南

ALNS(Adaptive Large Neighborhood Search)是一款强大的Python组合优化算法库,专为解决复杂的组合优化问题设计。通过灵活的启发式搜索框架,ALNS能在大型解空间中高效寻找近似最优解,广泛应用于物流调度、生产规划和资源分配等领域。本文将带你从安装到实战,全面掌握这个优化神器! ## ???? 什么是ALNS?核心功能解析 ALNS算法的核心在于**自适应邻域搜...

gitblog_00235的博客 664

RK3588S之CPU--benchmark(一)

本章会基于RK3588S来介绍有关CPU常用的benchmark工具及测试过程

文艺小少年的博客 1499

第1章:认识OpenClaw

OpenClaw是一个开源的 AI 智能体 Gateway 网关项目名称演变:OpenClaw 原名 Clawdbot,因 Anthropic 商标顾虑在 2026年1月27日更名为 Moltbot(过渡名),最终在1月30日确定为 OpenClaw。三个名字本质是同一个项目,功能完全一致。🏠在你的电脑上运行:完全本地部署,数据隐私有保障📁访问本地文件:可以搜索、读取、编辑你电脑上的文件🔌扩展能力:通过Skills(技能)系统无限扩展功能💬多平台使用。

Maynor的博客 2835

ALNS优化算法完整教程:Python组合优化终极指南

你是否正在寻找解决复杂组合优化问题的高效方法?ALNS(自适应大邻域搜索)算法正是你需要的终极工具!🚀 ALNS是一种强大的元启发式算法,专门用于解决旅行商问题、车辆路径问题等经典的组合优化难题。这个Python实现的ALNS库为你提供了完整的算法框架,让你能够专注于问题本身,而不是算法实现。 ## 什么是ALNS优化算法? ALNS是自适应大邻域搜索算法的简称,它结合了多种启发式算子来系

gitblog_01178的博客 752

5、硬件工程师笔试面试——上拉电阻、下拉电阻(2025版本)

本文系统介绍了上拉电阻和下拉电阻的原理与应用。上拉电阻通过连接电源确保电路高电平状态,常用于数字电路防抖、OC门驱动等场景;下拉电阻连接地线维持低电平,适用于低电平触发电路。文章详细分析了两种电阻的选择方法、功耗与响应速度的平衡策略,以及在高速信号传输中的影响差异。同时对比了二者的优缺点,并提供了模拟电路中的应用实例。最后给出了电阻值计算的工程方法,为硬件工程师的电路设计提供了实用参考。

XU157303764的博客 2872

运筹系列39:ALNS使用

1. ALNS介绍 ALNS(Adaptive Large Neighborhood Search)是现在用的很多的启发式算法,发表于2010年。其基本思路是不断destroying和repairing问题。 定义XXX为问题III的可行解集,c(X)表示要优化的目标。我们从一个初始解x1出发,搜索N(x1)领域范围内的最优值x2,然后再搜索N(x2)范围内的最优值x3……,这是最优梯度下降法。 ...

kittyzc的博客 8178

【报错解决】OpenClaw 报错 ENOENT: no such file or directory, open ‘/root/.npmrc‘:配置文件路径不存在

本文分析了OpenClaw平台在国产操作系统环境下运行时出现的ENOENT: no such file or directory, open '/root/.npmrc'错误。该问题源于容器环境中root用户的npm配置文件缺失,导致插件依赖安装失败。文章详细解释了npm配置加载机制和容器环境的特殊性,提出了四种解决方案:手动创建配置文件、写入基础配置、使用全局配置替代以及容器启动时自动初始化。最后指出该错误暴露了工程中对默认路径盲目信任的问题,强调显式声明运行环境依赖的重要性。

博客 2016

matlab 一维相位解缠

一维相位解缠算法的matlab代码实现。

点云侠的博客 373
上一篇: Python实现VRP常见求解算法——离散量子行为粒子群算法(DQPSO)
下一篇: Python实现OSM地图数据解析——OSM2GMNS
Better.C
博客等级 码龄9年 1万+粉丝 72原创
评论 94
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

Better.C

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

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

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

打赏作者

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

抵扣说明:

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

余额充值