python实现01背包问题——蛮力法+模拟退火算法
说明
背包问题可分为01背包问题、完全背包问题和分组背包问题等,这里只讨论01背包问题。
1、初始化工作
背包中物品的重量、体积和价值使用python的random库随机产生,并设置seed(x)随机种子以便复现。模拟退火算法还要设置一些超参数:初始温度、退火率、平衡次数、迭代次数、终止搜索期望值、终止温度。
2、模拟退火算法实现背包问题步骤
(1)初始化,产生一个初始解,并且这个解要符合重量和体积的要求。
(2)计算当前解的背包价值。
(3)随机选取某个物品,如果物品在背包中则取出随机放入其它的物品,如果不在背包中则直接加入物品,并计算新的解,并且这个解要符合重量和体积的要求,不能超体积和超重。
(4)如果新的解更好,则直接接受新的解;如果新的解更差,则以概率
接受这个更差的解;如果超出体积或重量阈值,放弃这个解。
(5)达到退火平衡次数时,以某个退火率下降温度,并开心新的一轮迭代。
(6)跳出搜索条件:温度下降到设置的某温度;或达到人为设定的期望值。
3、模拟退火算法调参——根据蛮力法的最优解辅助调参
对于背包问题,启发式算法算出的不一定是最优解,而算法的好坏需要对参数进行调整,迭代次数过多会导致时间浪费,迭代次数过少又会导致局部最优解太差。因此,假设已知背包问题的一些最优值,那么就有助于模拟退火算法调出不错的参数。当然,问题规模大到蛮力法无法求解时,那就没有了最优值参考,一般来说迭代次数越多,尝试方案越多,局部最优解越靠近全局最优解。
参数没调好的例子如下图所示,在规模不大的情况下,此时模拟退火算出的值距离最优值都有明显差距,则说明超参数设置不好,需要进一步调参:
4、调好参数后的结果
此时超参数为温度=200,退火率=0.95,平衡次数=500,迭代次数=100,在物品数量5-12个的情况都找到了最优解(曲线完全重合)。此参数仅供参考,只适合在实验规模内的背包问题,在背包问题规模很大时,一般需要把参数再调高,迭代次数需要更大。
由于模拟退火算法是启发式算法,时间复杂度由超参数决定,这里的时间复杂度可以表示为o(mn),m为外层迭代次数,n为内层退火平衡次数。
代码
"""
假设有N件物品,每件物品有其价值Ui、重量Wi和体积Vi,此处1<=i<=N。背包有承重限制和容量限制,即所有装到背包中的物品总重量不大于W、总体积不大于V。
以上体积、重量、价值均为正整数。请设计一种算法,在上述条件下使背包中装入物品的总价值最大,并给出算法的时间、空间复杂度分析。本作业提交代码和调研分
析文档。文档按规定的字体和行间距等进行排版(同上),文档提交PDF版本即可,通常在2页左右,文档中不排版代码。
"""
import random
import math
import time
from itertools import permutations
import matplotlib.pyplot as plt
# 使能够正常显示中文
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
"""方法一——蛮力法:简单粗暴遍历所有结果找最优解"""
class BruteForce(object):
def __init__(self, weight_list, volume_list, value_list, Weight_threshold_value, Volume_threshold_value):
self.weight_list = weight_list
self.volume_list = volume_list
self.value_list = value_list
self.Weight_threshold_value = Weight_threshold_value
self.Volume_threshold_value = Volume_threshold_value
self.total_weight = 0
self.total_volume = 0
self.total_value = 0
self.current_max_value = self.total_value
self.object_number = [] # 选的物品编号
self.all_object_number_dict = {} # 所有符合的方案
self.indexes = [] # 物品索引列表
for i in range(len(self.weight_list)):
self.indexes.append(i)
# print(f"indexes:{indexes}")
def calculate_value(self, object_number_list):
"""取的方案物品编号object_number_list"""
# 符合取的数量要求
self.total_weight = 0
self.total_volume = 0
self.total_value = 0
if object_number_list:
for i in object_number_list:
self.total_weight = self.total_weight + weight_list[i]
self.total_volume = self.total_volume + volume_list[i]
self.total_value = self.total_value + value_list[i]
if self.total_weight <= Weight_threshold_value and self.total_volume <= Volume_threshold_value:
self.all_object_number_dict[self.total_value] = object_number_list
if self.total_value > self.current_max_value:
self.current_max_value = self.total_value
# print(
# f"方案object_number:{object_number_list}符合要求,total_weight:{total_weight},total_volume:{total_volume},total_value:{total_value}")
else:
# print(
# f"方案object_number:{object_number_list}不符合条件,total_weight:{total_weight},total_volume:{total_volume},total_value:{total_value}")
pass
def run(self):
# 可能选1-30件物品
for i in range(1, len(weight_list) + 1):
# 选不同的物品 全部可能排列组合一遍
for j in permutations(self.indexes, i):
object_number = list(j)
if len(object_number) == i:
self.calculate_value(object_number_list=object_number)
# print(f"所有符合的方案all_object_number_dict:{all_object_number_dict}")
# print(f"方案总数:len(all_object_number_dict):{len(self.all_object_number_dict)}")
print(
f"最好的选择方案是取第best_object_number:{self.all_object_number_dict[self.current_max_value]}个物品,total_value:{self.current_max_value}")
"""end"""
"""方法二——启发式算法——模拟退火算法"""
class SimulatedAnnealing(object):
def __init__(self, weight_list, volume_list, value_list, Weight_threshold_value, Volume_threshold_value, satisfying_value, break_T):
"""背包物体属性"""
self.object_total_number = len(weight_list)
self.weight_list = weight_list
self.volume_list = volume_list
self.value_list = value_list
self.Weight_threshold_value = Weight_threshold_value
self.Volume_threshold_value = Volume_threshold_value
self.best_value = -1 # 更新最优值
self.cur_total_weight = 0
self.cur_total_volume = 0
self.cur_total_value = 0
self.best_indexs_way = [0] * self.object_total_number
self.current_indexs_way = [0] * self.object_total_number # best_way 记录全局最优解方案 now_way 记录当前解方案
self.weight = self.weight_list
self.value = self.value_list
self.volume = self.volume_list
"""跳出条件"""
self.satisfying_value = satisfying_value
self.break_T = break_T
"""模拟退火属性"""
self.T = 200.0 # 温度
self.af = 0.95 # af退火率
self.balance = 500 # 平衡次数
self.iter_times = 100 # 迭代次数
def initialize(self):
"""初始化,产生随机解"""
while True:
for k in range(self.object_total_number):
if random.random() < 0.5:
self.current_indexs_way[k] = 1
else:
self.current_indexs_way[k] = 0
self.calculate_value(self.current_indexs_way)
if self.cur_total_weight < self.Weight_threshold_value and self.cur_total_volume < self.Volume_threshold_value:
break
self.best_value = self.calculate_value(self.current_indexs_way)
self.copy_list(self.best_indexs_way, self.current_indexs_way)
def copy_list(self, a, b): # 复制函数 把b列表的值赋值a列表
for i in range(len(a)):
a[i] = b[i]
def calculate_value(self, x):
"""计算背包的总重量、总体积、总价值"""
self.cur_total_weight = 0
self.cur_total_volume = 0
self.cur_total_value = 0
for i in range(self.object_total_number):
self.cur_total_weight += x[i] * self.weight[i] # 当前总重量
self.cur_total_volume += x[i] * self.volume[i] # 当前总体积
self.cur_total_value += x[i] * self.value[i] # 当前总价值
return self.cur_total_value
def get_object(self, x): # 随机将背包中已经存在的物品取出
while True:
ob = random.randint(0, self.object_total_number - 1)
if x[ob] == 1:
x[ob] = 0
break
def put_object(self, x): # 随机放入背包中不存在的物品
while True:
ob = random.randint(0, self.object_total_number - 1)
if x[ob] == 0:
x[ob] = 1
break
def run(self):
self.initialize() # 初始化,产生初始解
for i in range(self.iter_times):
test_indexs_way = [0] * self.object_total_number
now_total_value = 0 # 当前背包价值
for i in range(self.balance):
now_total_value = self.calculate_value(self.current_indexs_way)
self.copy_list(test_indexs_way, self.current_indexs_way)
ob = random.randint(0, self.object_total_number - 1) # 随机选取某个物品
if test_indexs_way[ob] == 1: # 如果物品在背包中
self.put_object(test_indexs_way) # 随机放入背包中不存在的物品
test_indexs_way[ob] = 0 # 在背包中则将其拿出,并加入其它物品
else: # 不在背包中则直接加入或替换掉已在背包中的物品
if random.random() < 0.5:
test_indexs_way[ob] = 1
else:
self.get_object(test_indexs_way)
test_indexs_way[ob] = 1
temp_total_value = self.calculate_value(test_indexs_way)
if self.cur_total_weight > self.Weight_threshold_value or self.cur_total_volume > self.Volume_threshold_value:
continue # 非法解则跳过
if temp_total_value > self.best_value: # 如果新的解更好,更新全局最优
self.best_value = temp_total_value
self.copy_list(self.best_indexs_way, test_indexs_way)
if temp_total_value > now_total_value: # 如果新的解比当前解更好,直接接受新解
self.copy_list(self.current_indexs_way, test_indexs_way)
else:
g = 1.0 * (temp_total_value - now_total_value) / self.T
if random.random() < math.exp(g): # 概率接受劣解
self.copy_list(self.current_indexs_way, test_indexs_way)
self.T = self.T * self.af # 温度下降
"""跳出条件, 达到满意的解或者温度直接跳出"""
if self.best_value > self.satisfying_value or self.T < self.break_T:
break
# 方案转为索引的形式
best_object_number = []
for i in range(object_total_number):
if self.best_indexs_way[i]:
best_object_number.append(i)
print(f"最好的选择方案是取第best_object_number:{best_object_number}个物品,total_value:{self.best_value}")
"""#####################################################实验对比###################################################"""
way1_best_value_list = [] # 10种子下蛮力法求出的最优值
way2_best_value_list = [] # 模拟退火算法下的最优解
x_list = [i for i in range(5, 12, 1)]
way1_time_list = []
way2_time_list = []
for object_total_number in x_list:
"""定义物品重量、体积、价值、总重量阈值、总体积阈值"""
random.seed(10)
# object_total_number = 9 # 物品数
weight_list = random.sample(range(1, 100), object_total_number)
volume_list = random.sample(range(1, 100), object_total_number)
value_list = random.sample(range(1, 1000), object_total_number)
Weight_threshold_value = sum(weight_list) / 2 # 取总和值的一半算了?直接不用改动了
Volume_threshold_value = sum(volume_list) / 2
print(f"Weight_threshold_value:{Weight_threshold_value}")
print(f"Volume_threshold_value:{Volume_threshold_value}")
print(f"weight_list:{weight_list}")
print(f"volume_list:{volume_list}")
print(f"value_list:{value_list}")
"""end"""
print("方法一——蛮力法 简单粗暴遍历所有结果找最优解")
way1_start_time = time.time()
BruteForce_obj = BruteForce(weight_list=weight_list, volume_list=volume_list, value_list=value_list,
Weight_threshold_value=Weight_threshold_value,
Volume_threshold_value=Volume_threshold_value)
BruteForce_obj.run()
way1_end_time = time.time()
print(f"蛮力法耗时{round(way1_end_time - way1_start_time, 2)}s")
way1_best_value_list.append(BruteForce_obj.current_max_value)
way1_time_list.append(round(way1_end_time - way1_start_time, 2))
print("方法二——启发式算法——模拟退火算法")
way2_start_time = time.time()
satisfying_value = 999999 # 设置满意解,达到就直接退出了
break_T = 1 # 设置跳出温度
SimulatedAnnealing_obj = SimulatedAnnealing(weight_list=weight_list, volume_list=volume_list, value_list=value_list,
Weight_threshold_value=Weight_threshold_value,
Volume_threshold_value=Volume_threshold_value,
satisfying_value=satisfying_value, break_T=break_T)
SimulatedAnnealing_obj.run()
way2_end_time = time.time()
print(f"模拟退火法耗时{round(way2_end_time - way2_start_time, 2)}s")
way2_best_value_list.append(SimulatedAnnealing_obj.best_value)
way2_time_list.append(round(way2_end_time - way2_start_time, 2))
"""end"""
print(f"way1_best_value_list:{way1_best_value_list}")
print(f"way1_time_list:{way1_time_list}")
print(f"way2_best_value_list:{way2_best_value_list}")
print(f"way2_time_list:{way2_time_list}")
plt.plot(x_list, way1_best_value_list)
plt.plot(x_list, way2_best_value_list)
plt.xlabel("物品数")
plt.ylabel("最优值")
plt.title(f"蛮力法和模拟退火算法最优值")
plt.legend(['蛮力法', '模拟退火算法'])
plt.show()
plt.plot(x_list, way1_time_list)
plt.plot(x_list, way2_time_list)
plt.xlabel("物品数")
plt.ylabel("最优值花费时间(s)")
plt.title(f"蛮力法和模拟退火算法花费时间(s)")
plt.legend(['蛮力法', '模拟退火算法'])
plt.show()
本文来自博客园,作者:JaxonYe,转载请注明原文链接:https://www.cnblogs.com/yechangxin/articles/17017591.html
侵权必究