考虑体积重量的01背包问题—基于遗传算法
1 背包问题简介
经典背包问题可以描述为:给定一组物品,每种物品都有自己的重量/体积和价值,要求把一定数量的物品放入背包,在满足背包载重/容积的约束下,最大化背包内的物品价值。
2 场景设计
已知货物的重量和体积,在满足背包载重和容积约束的情况下,最大化背包内的物品价值。
3 遗传算法设计
3.1 算子设计
采用二进制编码、锦标赛选择、顺序交叉、基本位变异、一对一生存者竞争。
二进制编码中的01串对应于货物序号,1表示包对应序号的货物放入背包中,0表示不放入,上面的例子中货物1、4、5、6需要放入背包中。
3.2 适应度设计
适应度用打包的物品的价值进行表征。但是01编码有一个比较大的特点:载重容积有溢出的可能,以3.1图中的编码为例子,取出的1、4、5、6货物可能放不进背包中,这就是不满足约束的解,对此引入惩罚:如果超出约束,取价值的相反数,这样表征对于超过约束的项,价值少的(即货物相对较少)反而是比较好的待优化解,后续操作更容易获得满足约束的解。
3.3 遗传算法设计
import random
import pandas as pd
def package_calFitness(cargo_df,pop,v,m):
"""
#计算适应度:解满足约束取正值,不满足约束取负值
输入:cargo_df-货物列表,pop-个体,v-背包容积,m-背包载重
返回:适应值-fit
"""
v_sum,m_sum,values_sum= 0,0,0
for j in range(len(pop)):
if pop[j]==1:
v_sum += cargo_df.loc[j,'体积']
m_sum += cargo_df.loc[j,'重量']
values_sum += cargo_df.loc[j,'价值']
#计算(不满足约束的为相反数,即惩罚)
if (v_sum<=v) & (m_sum<=m):
fit = values_sum
else:
fit = -values_sum
return round(fit,3)
def tournament_select(population,popsize,fit,tournament_size):
"""
锦标赛选择
输入:population-种群,popsize-种群大小,fit-适应度,tournament_size-锦标赛小组个数
返回:选择后种群-new_population
"""
new_population = []
while len(new_population)<len(population):
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 = population[int(tournament_df.iloc[-1,0])]
new_population.append(new_pop)
return new_population
def package_crossover(popsize,parent1_pop,parent2_pop,pc):
"""
顺序交叉
输入:popsize-种群大小,parent1_pop-父代,parent2_pop-母代,pc-交叉概率
返回:交叉后种群-child_population
"""
child_population = []
for i in range(popsize):
parent1 = parent1_pop[i]
parent2 = parent2_pop[i]
child = [-1]*len(parent1)
if random.random() >= pc:
child = parent1#随机生成一个
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()
child[0:start_pos] = parent2[0:start_pos].copy()
child[end_pos+1:] = parent2[end_pos+1:].copy()
child_population.append(child)
return child_population
def package_mutate(populations,pm):
"""
基本位变异
输入: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:
if pop[i] == 0:pop[i] = 1
else:pop[i] = 0
population_after_mutate.append(pop)
return population_after_mutate
def package_ga(cargo_df,v,m,pc,pm,tournament_size,popsize,generations):
"""
遗传算法主流程
"""
Chrom = [[random.randint(0,1) for i in range(len(cargo_df))] for j in range(popsize)]
fit = [-1]*popsize
for i in range(popsize):fit[i] = package_calFitness(cargo_df,Chrom[i],v,m)
best_fit = max(fit)
best_pop= Chrom[fit.index(max(fit))].copy()
iter = 0
while iter<generations:
Chrom1 = tournament_select(Chrom,popsize,fit,tournament_size)
Chrom2 = tournament_select(Chrom,popsize,fit,tournament_size)
new_Chrom = package_crossover(popsize,Chrom1,Chrom2,pc)
new_Chrom = package_mutate(new_Chrom,pm)
iter +=1
new_fit = [-1]*popsize
for i in range(popsize):new_fit[i] = package_calFitness(cargo_df,new_Chrom[i],v,m)
for i in range(popsize):
if fit[i] < new_fit[i]:
Chrom[i] = new_Chrom[i]
fit[i] = new_fit[i]
if best_fit < max(fit):
best_fit = max(fit)
best_pop= Chrom[fit.index(max(fit))].copy()
print(iter,best_fit)
v_sum,m_sum = 0,0
package_list,unpackage_list = [],[]
for j in range(len(best_pop)):
if best_pop[j]==0:
unpackage_list.append(cargo_df.loc[j,'货物序号'])
else:
package_list.append(cargo_df.loc[j,'货物序号'])
v_sum += cargo_df.loc[j,'体积']
m_sum += cargo_df.loc[j,'重量']
return package_list,unpackage_list,v_sum,m_sum,best_fit
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]#重量
value = [5,5,7,7,9,2,2,6,8,3,9,4,5,3,7,10,5,6,1,2,9,8,8,9,5,2,1,8,10,6,3,2,10,10,9,8,7,6,2,8,5,1,7,5,6,2,2,7,6,10,
5,4,8,9,1,7,8,8,4,7,5,5,6,5,1,6,8,5,2,6,9,9,6,9,7,5,4,6,3,10,7,4,8,8,4,10,9,2,10,5,9,10,6,10,4,3,7,5,7,9]#价值
cargo_df = pd.DataFrame({'货物序号':num, "体积":volumns, "重量":weight,"价值":value})
#参数
M,V= 200,200 #箱子载重容积
generations = 100
popsize = 40
tournament_size = 4
pc = 0.9
pm = 0.1
package_list,unpackage_list,v_sum,m_sum,fit = package_ga(cargo_df,V,M,pc,pm,tournament_size,popsize,generations)
print("需打包的商品序号:",package_list)
print("背包容积载重利用情况价值分别是:",v_sum,m_sum,fit)
3.4 结果
需打包的商品序号: [0, 1, 3, 4, 7, 10, 13, 15, 17, 19, 20, 22, 24, 27, 28, 29, 33, 35, 39, 40, 45, 46, 49, 50, 55, 56, 59, 61, 62, 71, 73, 75, 77, 79, 80, 82, 84, 85, 89, 91, 98]
背包容积载重利用情况价值分别是: 197,191,281。
**【讨论】**这里设置M,V= 200,200,当M和V设置比较小时,需要适当提高迭代次数,因为货物较多时,随机生成的序列中,01串中的0和1数量是接近的,而MV值较小时,对于解的要求就是1少0多,那么需要一定的代数去迭代得到较优解。
背包问题系列
考虑体积重量的装箱问题(贪婪策略装箱)— 基于遗传算法
考虑体积重量的装箱问题(箱子装载平衡)— 基于遗传算法
记录学习过程,欢迎指正