zoukankan      html  css  js  c++  java
  • 0-1 背包问题

    算法打卡第29天

    前言

      虽然跟着振哥打卡了这么久,好多东西不复习早就都已经忘完了,于是今天格外的兴奋,想到了我每天第一时刻都会关注的博客来记录我之后的算法历程,

    此刻,2021年2月2号星期2(还是源于我老爸不经意的一句话),现在的我还是一个小白白,刚过完Python 基础,面向对象才摸到了皮毛,后面还有很多严峻的挑战,

    不知道几年后看到自己的这篇文章,想到自己最初的样子会是一个怎样的心情,也期待那时的我会在哪个城市。突然提笔还不知道怎么继续,可能现在是最迷茫的时候,

    面临着毕业,等待着成绩,期盼着未来。

    鸡汤:

      山脉没有起伏怎么会有人称赞山峰的巍峨,大海没有波涛又怎会有人称赞浪花的美丽

     

    一.算法简介(wiki)

      背包问题(Knapsack problem)是⼀种组合优化的NP完全问题。问题可以描述为给定⼀组物品,每种物品都有⾃⼰的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。 
      相似问题经常出现在商业、组合数学,计算复杂性理论、密码学和应⽤数学等领域中。也可以将背包问题描述为决定性问题,即在总重量不超过W的前提下,总价值是否能达到V。 
      0-1 背包是一个经典的组合优化问题,其中的思想非常重要。今天我们以一个简单的例子,先来体会 0-1 背包问题。
      有一个最大承重量为w 的背包,第i 件物品的价值为a1[i] ,第i 件物品的重量为a2[i] ,将物品装入背包,求解背包内最大的价值总和可以为多少?
    例子:
    a1 = [100, 70, 50, 10], a2 = [10, 4, 6, 12], w = 12, 背包内的最大价值总和为 120,分别装入重量
    为4和6的物品,能获得最大价值为 120,补全下面代码,返回求解的最大价值:
    def f(a1,a2,w):
        pass

    二.分析过程

    如下图所示:

    第一行物品价值,第二行物品重量,我们从最右侧开始决策是否装入重量为12的物品:

    背包可装最大重量恰好为 12

    • 如果选择装入此物品,背包内物品价值为 10,并且已经不能再装入,因此得到一种可行解:价值为 10
    • 如果选择不装入,我们的视线移动到下一个物品决策上,同样地我们会面临装入还是不装入的两个可选择项:
      • 如果选择装入,创造 50 价值,并且还能最多装入重量为6的物品:

    代码实现:

    a1 = [100, 70, 50, 10]
    a2 = [10, 4, 6, 12]
    w = 12
    
    
    def f(i, w):
        if w == 0 or i < 0:
            return 0
        elif a2[i] > w:
            return f(i-1, w)
        return max(a1[i] + f(i-1, w-a2[i]), f(i-1, w))
    
    
    r = f(3, w)
    print(r)

     三.动态规划

      我们需要选择n个元素中的若干个来形成最优解,假定为k个。那么对于这k个元素a1, a2, ...ak来说,它们组成的物品组合必然满足总重量<=背包重量限制,而且它们的价值必然是最大的。因为它们是我们假定的最优选择嘛,肯定价值应该是最大的。假定ak是我们按照前面顺序放入的最后一个物品。它的重量为wk,它的价值为vk。既然我们前面选择的这k个元素构成了最优选择,如果我们把这个ak物品拿走,对应于k-1个物品来说,它们所涵盖的重量范围为0-(W-wk)。假定W为背包允许承重的量。假定最终的价值是V,剩下的物品所构成的价值为V-vk。这剩下的k-1个元素是不是构成了一个这种W-wk的最优解呢?
    我们可以用反证法来推导。假定拿走ak这个物品后,剩下的这些物品没有构成W-wk重量范围的最佳价值选择。那么我们肯定有另外k-1个元素,他们在W-wk重量范围内构成的价值更大。如果这样的话,我们用这k-1个物品再加上第k个,他们构成的最终W重量范围内的价值就是最优的。这岂不是和我们前面假设的k个元素构成最佳矛盾了吗?所以我们可以肯定,在这k个元素里拿掉最后那个元素,前面剩下的元素依然构成一个最佳解。
    现在我们经过前面的推理已经得到了一个基本的递推关系,就是一个最优解的子解集也是最优的。可是,我们该怎么来求得这个最优解呢?我们这样来看。假定我们定义一个函数c[i, w]表示到第i个元素为止,在限制总重量为w的情况下我们所能选择到的最优解。那么这个最优解要么包含有i这个物品,要么不包含,肯定是这两种情况中的一种。如果我们选择了第i个物品,那么实际上这个最优解是c[i - 1, w-wi] + vi。而如果我们没有选择第i个物品,这个最优解是c[i-1, w]。这样,实际上对于到底要不要取第i个物品,我们只要比较这两种情况,哪个的结果值更大不就是最优的么?
    在前面讨论的关系里,还有一个情况我们需要考虑的就是,我们这个最优解是基于选择物品i时总重量还是在w范围内的,如果超出了呢?我们肯定不能选择它,这就和c[i-1, w]一样。
    这里有一点值得注意,这里的wi指的是第i个物品的重量,而不是到第i个物品时的总重量。
    另外,对于初始的情况呢?很明显c[0, w]里不管w是多少,肯定为0。因为它表示我们一个物品都不选择的情况。c[i, 0]也一样,当我们总重量限制为0时,肯定价值为0。
    这样,基于我们前面讨论的这3个部分,我们可以得到一个如下的递推公式:

     
     

    有了这个关系,我们可以更进一步的来考虑代码实现了。我们有这么一个递归的关系,其中,后面的函数结果其实是依赖于前面的结果的。我们只要按照前面求出来最基础的最优条件,然后往后面一步步递推,就可以找到结果了。
    我们再来考虑一下具体实现的细节。这一组物品分别有价值和重量,我们可以定义两个数组int[] v, int[] w。v[i]表示第i个物品的价值,w[i]表示第i个物品的重量。为了表示c[i, w],我们可以使用一个int[i][w]的矩阵。其中i的最大值为物品的数量,而w表示最大的重量限制。按照前面的递推关系,c[i][0]和c[0][w]都是0。而我们所要求的最终结果是c[n][w]。所以我们实际中创建的矩阵是(n + 1) x (w + 1)的规格。
     
    代码实现

    import numpy as np
    
    
    def solve(vlist,wlist,totalWeight,totalLength):
        resArr = np.zeros((totalLength+1,totalWeight+1),dtype=np.int32)
        for i in range(1,totalLength+1):
            for j in range(1,totalWeight+1):
                if wlist[i] <= j:
                    resArr[i,j] = max(resArr[i-1,j-wlist[i]]+vlist[i],resArr[i-1,j])
                else:
                    resArr[i,j] = resArr[i-1,j]
        return resArr[-1,-1]
    
    if __name__ == '__main__':
        v = [0, 20, 200, 120]
        w = [0, 10, 20, 30]
        weight = 50
        n = 3
        result = solve(v, w, weight, n)
        print(result)

    参考链接:https://www.jianshu.com/p/25f4a183ede5

  • 相关阅读:
    2017 年终总结 —— 在路上
    尝试造了个工具类库,名为 Diana
    走近 Python (类比 JS)
    Node.js 异步异闻录
    使用 Node.js 搭建一个 API 网关
    不就是语法和长难句吗—笔记总结Day4
    不就是语法和长难句吗—笔记总结Day3
    不就是语法和长难句吗—笔记总结Day2
    不就是语法和长难句吗—笔记总结Day1
    Kali Day1
  • 原文地址:https://www.cnblogs.com/Gaowaly/p/14360681.html
Copyright © 2011-2022 走看看