0-1背包问题项目实战

一.引言


背包问题是一个在算法设计和优化领域中非常重要的问题,它涉及到如何在有限资源下做出最优选择。以下是对背包问题算法理解的简要介绍,以及它在算法中的重要性。

算法理解

1.问题定义:背包问题要求在不超过给定容量限制的情况下,从一组物品中选择物品,以最大化总价值。

2.贪心算法:一种简单直观的方法是按照物品价值与重量比率的顺序选择物品,但这种方法并不总是能得到最优解。

3.动态规划:一种更有效的方法是使用动态规划(DP)。DP算法通过构建一个表格来记录不同容量下的最大价值,然后通过状态转移方程来填充这个表格。

4.状态表示:在DP中,通常使用一个二维数组,其中第一维表示物品的索引,第二维表示背包的容量。

5.状态转移:对于每个物品,我们考虑两种情况:包含它和不包含它。通过比较这两种情况下的最大价值,我们可以决定是否将该物品放入背包。

6.最优子结构:背包问题具有最优子结构特性,即问题的最优解可以通过其子问题的最优解来获得。

7.递归方法:除了DP,还可以使用递归方法来解决背包问题,但通常需要配合记忆化搜索来避免重复计算。

重要性

1.教育工具:背包问题是算法课程中常用的教学工具,帮助学生理解贪心选择、动态规划和递归等基本概念。

2.优化问题原型:它是一个典型的优化问题,许多实际问题都可以抽象成背包问题的形式,从而使用背包问题的解法来解决。

3.NP完全性:背包问题及其变体(如0-1背包问题)是NP完全问题,这有助于理解计算复杂性理论和算法的局限性。

4.启发式和近似算法:由于背包问题的计算复杂性,研究者开发了多种启发式和近似算法来寻找可行解,这些算法在解决大规模问题时非常有用。

5.算法性能评估:背包问题常被用作评估新算法性能的基准问题,特别是在算法竞赛和算法研究中。

6.实际应用:背包问题在资源分配、投资组合优化、供应链管理等领域有广泛的应用,对实际问题的解决具有指导意义。

二.实战演示:

本次例子为0-1背包问题:有 N 件物品和一个承重为 C 的背包(也可定义为体积),每件物品的重量是 wi,价值是 pi,求解将哪几件物品装入背包可使这些物品在重量总和不超过 C 的情况下价值总和最大。这个问题隐含了一个条件,每个物品只有一件,也就是限定每件物品只能选择 0 个或 1 个,因此又被称为 0-1 背包问题。

来看一个具体的例子,有一个背包,最多能承载重量为 C=150 的物品,现在有 7 个物品(物品不能分割成任意大小),编号为 1~7,重量分别是 wi=[35、30、60、50、40、10、25],价值分别是 pi=[10、40、30、50、35、40、30],现在从这 7 个物品中选择一个或多个装入背包,要求在物品总重量不超过 C 的前提下,所装入的物品总价值最高。

针对这个问题,有三种贪婪策略的选择问题。

第一:根据最小重量贪心策略,这个策略每次选择最小重量的物品,最终选择装入背包的物品编号依次是 4、2、6、5,此时包中物品总重量是 130,总价值是 165;

第二:根据最大价值贪心策略,这个策略每次都选择重量最轻的物品,根据这个策略最终选择装入背包的物品编号依次是 6、7、2、1、5,此时包中物品总重量是 140,总价值是 155;

第三:根据取最大价值密度贪心策略,这个策略是定义一个价值密度的概念,每次选择都选价值密度最高的物品,物品的价值密度 si 定义为 pi/wi,这 7 件物品的价值密度分别为 si=[0.286、1.333、0.5、1.0、0.875、4.0、1.2]。根据这个策略最终选择装入背包的物品编号依次是 6、2、7、4、1,此时包中物品的总重量是 150,总价值是 170。

【数据定义】

# 0-1背包问题的实现
class Goods:
    def __init__(self, weight, value, status):
        # 物品的重量
        self.weight = weight
        # 物品的价值
        self.value = value
        # 0表示未放入包,1表示已经放入包
        self.status = status

三种策略定义

class Greedy(object):
    def greed(self,goods,total,parameter=None):
        result = []
        sum_weight = 0
        sum_value = 0
        while True:
            s = self.strategy(goods, total,parameter)
            if s == -1:
                break
            sum_weight += goods[s].weight
            sum_value += goods[s].value
            if parameter == 'weight':
                result.append(goods[s].weight)
                total = total - goods[s].weight
                goods[s].status = 1
                goods.pop(s)
            elif parameter == 'value':
                result.append(goods[s].value)
                total = total - goods[s].weight
                goods[s].status = 1
                goods.pop(s)
            elif parameter == 'si':
                # 保留两位有效数字
                res = round(goods[s].value / goods[s].weight,2)
                result.append(res)
                total = total - goods[s].weight
                goods[s].status = 1
                goods.pop(s)
        return result,sum_weight,sum_value

    def strategy(self,goods,total,parameter=None):
        index = -1
        minWeight = goods[0].weight
        maxValue = goods[0].value
        maxSi = maxValue / minWeight
        for i in range(0, len(goods)):
            currentGood = goods[i]
            if parameter == 'weight':
                if currentGood.status == 0 and currentGood.weight <= total and currentGood.weight <= minWeight:
                    index = i
                    minWeight = goods[index].weight
            elif parameter == 'value':
                if currentGood.status == 0 and currentGood.weight <= total and currentGood.value > maxValue:
                    index = i
                    maxValue = currentGood.value
            elif parameter == 'si':
                if currentGood.status == 0 and currentGood.weight <= total and currentGood.value / currentGood.weight > maxSi:
                    index = i
                    maxSi = currentGood.value / currentGood.weight
                if currentGood.value / currentGood.weight <= maxSi and currentGood.weight==total:
                    index = i
        return index

三种策略调用

if __name__ == '__main__':
    parameter_list = ['weight','value','si']
    for parameter in parameter_list:
        goods = [Goods(35, 10, 0), Goods(30, 40, 0), Goods(60, 30, 0), Goods(50, 50, 0),
                 Goods(40, 35, 0), Goods(10, 40, 0), Goods(25, 30, 0)]
        g = Greedy()
        result, sum_weight, sum_value = g.greed(goods, 150, parameter=parameter)
        if parameter == 'weight':
            print("--------------按照取最小重量贪心策略--------------")
            print("最终总重量为:" + str(sum_weight))
            print("最终总价值为:" + str(sum_value))
            print("重量选取依此选择为:", end='')
            print(result)
        elif parameter == 'value':
            print("--------------按照取最大价值贪心策略--------------")
            print("最终总重量为:" + str(sum_weight))
            print("最终总价值为:" + str(sum_value))
            print("价值选取依此选择为:", end='')
            print(result)
        elif parameter == 'si':
            print("--------------按照取最大价值密度贪心策略--------------")
            print("最终总重量为:" + str(sum_weight))
            print("最终总价值为:" + str(sum_value))
            print("密度选取依此选择为:", end='')
            print(result)

三种策略实现结果与分析

--------------按照取最小重量贪心策略--------------
最终总重量为:140
最终总价值为:155
重量选取依此选择为:[10, 25, 30, 35, 40]
--------------按照取最大价值贪心策略--------------
最终总重量为:130
最终总价值为:165
价值选取依此选择为:[50, 40, 40, 35]
--------------按照取最大价值密度贪心策略--------------
最终总重量为:150
最终总价值为:170
密度选取依此选择为:[4.0, 1.33, 1.2, 1.0, 0.29]

看起来第三种策略取得了最好的结果,和动态规划方法得到的最优结果是一致的,但是实际上,这只是对这组数据的验证结果而已,如果换一组数据,结果可能完全相反。当然,对于一些能够证明贪婪策略得到的就是最优解的问题,应用贪婪法可以高效地求得结果,比如求最小生成树的 Prim 算法和 Kruskal 算法。

在大多数情况下,贪婪法受自身策略模式的限制,通常很难直接求解全局最优解问题,也很难用于多阶段决策问题。贪婪法只能得到比较接近最优解的近似的最优解,但是作为一种启发式辅助方法在很多算法中都得到了广泛的应用,很多常用的算法在解决局部最优决策时,都会应用到贪婪法。比如 Dijkstra 的单源最短路径算法在从 dist 中选择当前最短距离的节点时,就是采用的贪婪法策略。事实上,在任何算法中,只要在某个阶段使用了只考虑局部最优情况的选择策略,都可以理解为使用了贪婪算法。

  • 28
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值