python实现01背包问题——蛮力法+模拟退火算法

说明

背包问题可分为01背包问题、完全背包问题和分组背包问题等,这里只讨论01背包问题。
1、初始化工作
背包中物品的重量、体积和价值使用python的random库随机产生,并设置seed(x)随机种子以便复现。模拟退火算法还要设置一些超参数:初始温度、退火率、平衡次数、迭代次数、终止搜索期望值、终止温度。
2、模拟退火算法实现背包问题步骤
(1)初始化,产生一个初始解,并且这个解要符合重量和体积的要求。
(2)计算当前解的背包价值。
(3)随机选取某个物品,如果物品在背包中则取出随机放入其它的物品,如果不在背包中则直接加入物品,并计算新的解,并且这个解要符合重量和体积的要求,不能超体积和超重。
(4)如果新的解更好,则直接接受新的解;如果新的解更差,则以概率

接受这个更差的解;如果超出体积或重量阈值,放弃这个解。
(5)达到退火平衡次数时,以某个退火率下降温度,并开心新的一轮迭代。
(6)跳出搜索条件:温度下降到设置的某温度;或达到人为设定的期望值。
3、模拟退火算法调参——根据蛮力法的最优解辅助调参
对于背包问题,启发式算法算出的不一定是最优解,而算法的好坏需要对参数进行调整,迭代次数过多会导致时间浪费,迭代次数过少又会导致局部最优解太差。因此,假设已知背包问题的一些最优值,那么就有助于模拟退火算法调出不错的参数。当然,问题规模大到蛮力法无法求解时,那就没有了最优值参考,一般来说迭代次数越多,尝试方案越多,局部最优解越靠近全局最优解。
参数没调好的例子如下图所示,在规模不大的情况下,此时模拟退火算出的值距离最优值都有明显差距,则说明超参数设置不好,需要进一步调参:
image

4、调好参数后的结果
此时超参数为温度=200,退火率=0.95,平衡次数=500,迭代次数=100,在物品数量5-12个的情况都找到了最优解(曲线完全重合)。此参数仅供参考,只适合在实验规模内的背包问题,在背包问题规模很大时,一般需要把参数再调高,迭代次数需要更大。
由于模拟退火算法是启发式算法,时间复杂度由超参数决定,这里的时间复杂度可以表示为o(mn),m为外层迭代次数,n为内层退火平衡次数。
image
image
image

代码

"""
假设有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()





posted @ 2022-12-31 23:35  JaxonYe  阅读(1177)  评论(0编辑  收藏  举报