考虑体积重量的装箱问题(贪婪策略装箱)—— 基于遗传算法

考虑体积重量的装箱问题(贪婪策略装箱)—— 基于遗传算法

1 装箱问题简介

经典装箱问题要求把一定数量的物品放入容量相同的箱子中,在满足每个箱子装载物品的大小之和不超过箱子容量的约束下,最小化箱子数目。装箱问题是复杂的离散组合最优化问题,属于NP完全问题。

2 场景设计

已知货物的重量和体积,在满足箱子载重和容积约束的情况下,最小化箱子使用个数,并除了最后一个箱子外,前面的箱子尽量装满。

3 遗传算法设计

3.1 算子设计

采用自然数编码、锦标赛选择、顺序交叉、基本位变异、一对一生存者竞争。
染色体的自然数编码如下图所示,采用贪婪策略进行编码。
在这里插入图片描述

3.2 适应度设计

Fit = 总体积/(已使用车辆容积和+最后一辆车使用的容积)*总重量/(已使用车辆载重和+最后一辆车使用的载重)

这里适应度设计的目的在于让前面先装的箱子尽量装满,也就是在最小化箱子数的大目标下,使最后一辆车尽量少装货物。由于有两个参数–体积和质量,这里简单运用乘积进行平衡,可能不是最优的,但会是比较接近最优的。

3.3 遗传算法实现

# -*- coding: utf-8 -*-
import pandas as pd
import random

def tournament_select(pops,popsize,fit,tournament_size):
    new_pops = []
    while len(new_pops)<len(pops):
        tournament_list = random.sample(range(0,popsize),tournament_size)
        tournament_fit = [fit[i] for i in tournament_list]
        #转化为df方便索引
        tournament_df = pd.DataFrame([tournament_list,tournament_fit]).transpose().sort_values(by=1).reset_index(drop=True)
        new_pop = pops[int(tournament_df.iloc[0,0])]
        new_pops.append(new_pop)
        
    return new_pops

def crossover(popsize,parent1_pops,parent2_pops,pc):
    child_pops = []
    for i in range(popsize):
        parent1 = parent1_pops[i]
        parent2 = parent2_pops[i]
        child = [-1 for i in range(len(parent1))]
        
        if random.random() >= pc:
            child = parent1.copy()#随机生成一个
            random.shuffle(child)
            
        else:
            #parent1
            start_pos = random.randint(0,len(parent1)-1)
            end_pos = random.randint(0,len(parent1)-1)
            if start_pos>end_pos:start_pos,end_pos = end_pos,start_pos 
            child[start_pos:end_pos+1] = parent1[start_pos:end_pos+1].copy()
            # parent2 -> child
			list_index = list(range(end_pos+1,len(parent2)))+list(range(0,start_pos))
            j = -1
            for i in list_index:
                for j in range(j+1,len(parent2)):
                    if parent2[j] not in child:
                        child[i] = parent2[j]
                        break
                        
        child_pops.append(child)
    return child_pops

def mutate(pops,pm):
    pops_after_mutate = []
    mutate_time = 0
    for i in range(len(pops)):
        pop = pops[i].copy()
        if random.random() < pm: 
            while mutate_time<3:
                mut_pos1 = random.randint(0,len(pop)-1)  
                mut_pos2 = random.randint(0,len(pop)-1)
                if mut_pos1 != mut_pos2:pop[mut_pos1],pop[mut_pos2] = pop[mut_pos2],pop[mut_pos1]
                mutate_time += 1
        pops_after_mutate.append(pop)
        
    return pops_after_mutate


def package_calFitness(cargo_df,pop,max_v,max_m):
    '''
    输入:cargo_df-货物信息,pop-个体,max_v-箱子容积,max_m-箱子在载重
    输出:适应度-fit,boxes-解码后的个体
    '''
    box_num = 0#装满的箱子数
    v_sum,m_sum = 0,0
    v,m = 0,0
    boxes,box = [],[]
    for j in pop:
        v_j = cargo_df[cargo_df['货物序号']==j]['体积'].iloc[0]
        m_j = cargo_df[cargo_df['货物序号']==j]['重量'].iloc[0]
        v += v_j
        m += m_j
        if (v_sum+v_j <= max_v) and (m_sum+m_j <= max_m):
            box.append(j)
            v_sum = v_sum+v_j
            m_sum =m_sum+m_j
        else:
            boxes.append(sorted(box))
            box_num += 1
            box = []
            box.append(j)
            v_sum = v_j
            m_sum = m_j
            
    #最后一个箱子
    boxes.append(sorted(box))
    
    #fit=总体积/(已使用车辆容积和+最后一辆车使用容积)*总重量/(已使用车辆载重和+最后一辆车使用载重)
    fit = (v/(v_sum+box_num*max_v))*(m/(m_sum+box_num*max_m))
    return round(fit,4),boxes


def package_GA(cargo_df,generations,popsize,tournament_size,pc,pm,max_v,max_m):
    #初始化种群
    cargo_list = list(cargo_df['货物序号'])
    pops = [random.sample(cargo_list,len(cargo_list)) for i in range(popsize)]#种群初始化
    fit,boxes = [-1]*popsize,[-1]*popsize
    
    for i in range(popsize):
        fit[i],boxes[i] = package_calFitness(cargo_df,pops[i],max_v,max_m)
    
    best_fit = max(fit)
    best_pop = pops[fit.index(max(fit))]
    best_box = boxes[fit.index(max(fit))]
    
    if best_fit==1:return best_pop#1说明除最后一辆车都装满,已是最优解
        
    iter = 0#迭代计数
    while iter < generations:
        pops1 = tournament_select(pops,popsize,fit,tournament_size)
        pops2 = tournament_select(pops,popsize,fit,tournament_size)
        new_pops = crossover(popsize,pops1,pops2,pc)
        new_pops = mutate(new_pops,pm)
        iter += 1
        new_fit,new_boxes = [-1]*popsize,[-1]*popsize#初始化
        for i in range(popsize):
            new_fit[i],new_boxes[i] = package_calFitness(cargo_df,new_pops[i],max_v,max_m)#计算适应度
        for i in range(len(pops)):
            if fit[i] < new_fit[i]:
                pops[i] = new_pops[i]
                fit[i] = new_fit[i]
                boxes[i] = new_boxes[i]
        
        if best_fit < max(fit):#保留历史最优
            best_fit = max(fit)
            best_pop= pops[fit.index(max(fit))]
            best_box = boxes[fit.index(max(fit))]
            
        print("第",iter,"代适应度最优值:",best_fit)
    return best_pop,best_fit,best_box


if __name__ == '__main__':
    #数据
    num = list(range(100))#货物编号
    volumns = [1,6,7,8,1,2,3,1,8,8,10,1,9,3,4,3,5,7,4,6,5,5,9,5,6,3,9,9,6,3,4,2,1,3,5,9,6,6,8,5,6,2,7,9,5,1,7,5,10,6,
               4,6,9,7,2,4,3,7,5,4,5,10,2,1,4,10,9,6,10,10,10,2,10,2,4,6,4,1,7,6,1,10,1,3,4,1,7,3,6,5,3,10,6,8,1,6,4,4,10,3]#体积
    weight = [3,5,3,8,10,4,7,2,10,1,9,2,1,9,7,1,7,1,4,2,5,9,1,6,1,4,2,1,2,1,5,5,6,8,3,6,7,4,9,7,7,4,8,3,9,4,1,1,9,5,8,
              4,10,3,5,1,7,8,8,2,8,7,1,10,3,3,8,2,4,6,8,3,5,8,10,5,7,5,7,1,9,1,5,9,9,2,10,2,9,3,7,10,5,1,2,1,9,8,6,9]#重量
    cargo_df = pd.DataFrame({'货物序号':num, "体积":volumns, "重量":weight})
    
    M,V= 100,100 #箱子载重容积
    
    #GA参数
    generations = 50
    popsize = 40
    tournament_size = 4
    pc = 0.9
    pm = 0.1
    
    pop,fit,box = package_GA(cargo_df,generations,popsize,tournament_size,pc,pm,V,M)
    print("最优解:",box)
    

3.4 结果

最优解: [[0, 1, 6, 9, 14, 17, 21, 23, 24, 25, 31, 51, 52, 53, 57, 59, 82, 88, 98], [2, 5, 10, 12, 26, 34, 37, 38, 40, 54, 55, 64, 77, 78, 83, 87, 90, 94, 96, 97], [18, 19, 28, 43, 45, 47, 50, 56, 58, 60, 63, 67, 70, 71, 76, 79, 80, 81, 93, 99], [11, 13, 16, 29, 30, 32, 33, 35, 36, 39, 41, 46, 48, 49, 69, 75, 91, 95], [3, 4, 7, 8, 20, 22, 27, 42, 44, 62, 66, 68, 72, 73, 74, 85, 92], [15, 61, 65, 84, 86, 89]]
,总共需要6个箱子,箱子货物体积分别是[100, 98, 99, 97, 97, 39],重量分别是[96, 100, 98, 99, 97, 33]。

4 总结

装箱问题的目标是最小化箱子使用数量,在实际应用中,可能会有更细化的一个目标,比如先装货物的箱子尽量装满(最后一个箱子尽量少装),或者平衡所有箱子的载重和容积,这里实现的差别主要在于编码和适应度设计。本文求解的问题是尽量将前面先装的箱子装满,使用遗传算法进行求解,求解速度还是有点慢,不过从适应度上看求解效果应该还是不错的。
记录学习过程,欢迎指正

  • 11
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值