考虑体积重量的装箱问题(箱子装载平衡)— 基于遗传算法

考虑体积重量的装箱问题(箱子装载平衡)— 基于遗传算法

1 前言

经典装箱问题要求把一定数量的物品放入容量相同的箱子中,在满足每个箱子装载物品的大小之和不超过箱子容量的约束下,最小化箱子数目。在上一篇装箱问题的博文【考虑体积重量的装箱问题(贪婪策略装箱)—— 基于遗传算法】中,以贪婪策略进行装箱,在最小化箱子数量的前提下,追求先装货物的箱子尽量装满(或称最后一个箱子的装载最少),本文研究另一个目标,即在最小化箱子使用数量的情况下,平衡箱子的装载量。

2 场景设计

已知货物的重量和体积,在满足箱子载重和容积约束的情况下,最小化箱子使用数量,并追求箱子装载量平衡。

3 遗传算法设计

3.1 算子设计

采用整数编码、锦标赛选择、顺序交叉、基本位变异、一对一生存者竞争。
染色体的整数编码如下图所示,使用箱子数目进行编码,编码对应货物序号,如以下编码中,1号箱子装载货物1,6,2号箱子装载货物2,7,3号箱子装载货物3,8,4号箱子装载货物4,5。这种编码需要先给出箱子的数量,箱子的数量可由需求量估计得到最小所需数量,当最小箱子数量无法找到满足约束的解时,再增加箱子的数量重新进行求解,代码如下所示。
在这里插入图片描述

	#计算不考虑约束情况下最少说所需箱子数
    boxNum = math.ceil(max(cargo_df.loc[:,"体积"].sum()/V,cargo_df.loc[:,"重量"].sum()/M))
    #尝试进行求解
    while True:
        pop,fit,box,v_list,m_list = package_GA(cargo_df,generations,popsize,tournament_size,pc,pm,V,M,boxNum)
        if fit>0:#大于0表示求得可行解
            break
        else:
            boxNum += 1

3.2 适应度设计

Fit计算方法如下:

    if num == boxNum:#判断是否满足约束
        fit = 100/(np.var(v_sum) * np.var(m_sum))#构造方差积的倒数,常数100只是让结果数值显得大一点
    else:
        fit = -np.var(v_sum) * np.var(m_sum)#不满足约束做出惩罚

总体来说就是引入方差评价箱子装载的平衡,对于满足约束的情况,转换倒数后,fit越大说明箱子装载越平衡,而对于不满足约束的情况,fit越大表示不满足约束的情况较小,所以总的来看,倒数和“-”的作用就是使得满足约束或者不满足约束时,目标都是最大化fit。

3.3 遗传算法实现

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

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[-1,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:
            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()
            child[0:start_pos] = parent2[0:start_pos].copy()
            child[end_pos+1:] = parent2[end_pos+1:].copy()
            
        child_pops.append(child)
    return child_pops


def mutate(populations,pm,boxNum):
    """
    基本位变异
    输入:populations-种群,pm-变异概率
    返回:变异后种群-population_after_mutate
    """
    population_after_mutate = []
    for i in range(len(populations)):
        pop = populations[i].copy()
        for i in range(len(pop)):
            if random.random() < pm:
                randomList = list(range(1,boxNum+1))
                randomList.remove(pop[i])
                pop[i] = random.sample(randomList,1)[0]#随机生成另外一个箱子
                
        population_after_mutate.append(pop)
        
    return population_after_mutate


def package_calFitness(cargo_df,pop,max_v,max_m,boxNum):
    '''
    输入:cargo_df-货物信息,pop-个体,max_v-箱子容积,max_m-箱子在载重
    输出:适应度-fit,boxes-解码后的个体
    '''
    boxes = [[] for i in range(boxNum)]
    v_sum = [0]*boxNum
    m_sum = [0]*boxNum
    
    for j in range(len(pop)):
        box_id = int(pop[j])-1
        v_j = cargo_df[cargo_df['货物序号']==j]['体积'].iloc[0]
        m_j = cargo_df[cargo_df['货物序号']==j]['重量'].iloc[0]
        
        boxes[box_id].append(j)
        v_sum[box_id] += v_j
        m_sum[box_id] += m_j
    
    num = 0#计数
    for i in range(boxNum):
        if (v_sum[i] <= max_v) & (m_sum[i] <= max_m):
            num += 1
        else:
            break
        
    if num == boxNum:
        fit = 100/(np.var(v_sum) * np.var(m_sum))#构造方差和的倒数,100只是让结果数值显得大一点
    else:
        fit = -np.var(v_sum) * np.var(m_sum)#
    
    return round(fit,4),boxes,v_sum,m_sum


def package_GA(cargo_df,generations,popsize,tournament_size,pc,pm,max_v,max_m,boxNum):
    #初始化种群
    cargo_list = list(cargo_df['货物序号'])
    pops = [[random.randint(1,boxNum) for i in range(len(cargo_list))] for j in range(popsize)]#种群初始化
    
    fit,boxes = [-1]*popsize,[-1]*popsize
    v_sum,m_sum = [-1]*popsize,[-1]*popsize
    
    for i in range(popsize):
        fit[i],boxes[i],v_sum[i],m_sum[i] = package_calFitness(cargo_df,pops[i],max_v,max_m,boxNum)
    
    best_fit = max(fit)
    best_pop = pops[fit.index(max(fit))].copy()
    best_box = boxes[fit.index(max(fit))].copy()
    best_vsum = v_sum[fit.index(max(fit))].copy()
    best_msum = m_sum[fit.index(max(fit))].copy()
    
    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,boxNum)
        iter += 1
        new_fit,new_boxes = [-1]*popsize,[-1]*popsize#初始化
        newv_sum,newm_sum = [-1]*popsize,[-1]*popsize
        
        for i in range(popsize):
            new_fit[i],new_boxes[i],newv_sum[i],newm_sum[i] = package_calFitness(cargo_df,new_pops[i],max_v,max_m,boxNum)#计算适应度
        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]
                v_sum[i] = newv_sum[i]
                m_sum[i] = newm_sum[i]
        
        if best_fit < max(fit):#保留历史最优
            best_fit = max(fit)
            best_pop= pops[fit.index(max(fit))].copy()
            best_box = boxes[fit.index(max(fit))].copy()
            best_vsum = v_sum[fit.index(max(fit))].copy()
            best_msum = m_sum[fit.index(max(fit))].copy()
            
        print("第",iter,"代适应度最优值:",best_fit)
    return best_pop,best_fit,best_box,best_vsum,best_msum


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 #箱子载重容积
   
    generations = 100
    popsize = 40
    tournament_size = 4
    pc = 0.9
    pm = 0.1
    
    boxNum = math.ceil(max(cargo_df.loc[:,"体积"].sum()/V,cargo_df.loc[:,"重量"].sum()/M))
    
    while True:
        pop,fit,box,v_list,m_list = package_GA(cargo_df,generations,popsize,tournament_size,pc,pm,V,M,boxNum)
        if fit>0:
            break
        else:
            boxNum += 1
        
    print("最优解:",box)
    print("箱子载重分别为:",m_list)
    print("箱子容积分别为:",v_list)

3.4 结果

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

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

记录学习过程,欢迎指正

  • 8
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值