• 最清楚的01背包问题讲解


    题目:

    01背包问题描述:有编号分别为a,b,c,d,e的N=5件物品,它们的重量w分别是2,2,6,5,4,它们的价值v分别是6,3,5,4,6,每件物品数量只有一个,现在给你个承重为M=10的背包,如何让背包里装入的物品具有最大的价值总和sum_v?

    在DP(dynamic programming,动态规划)问题中,01背包问题是比较基础和简单的了,但是网上很多人的讲解要么长长一大段,长篇公式理论,要么就是知识把状态转移方程列了出来,而没有说明为什么方程是这么写的,下面我力图将01背包问题中最简单最核心的概念和思路讲一下

    1. 此01背包问题本质上是穷举背包容量和可供选择的物品(意思是里面的物品可能会放进背包,可能不会放进背包),取得最优解,只不过在穷举的过程中,会根据状态转移方程,只计算可能获得的最优解的部分,不去计算不是最优解的部分。具体来看,解题思路是把该问题分解为一个一个的小问题,一步步的通过小问题的最优解,最终得到大问题的最优解,跟我们人脑解题的思路是一样的。比如第一个小问题是“当我的背包承重M=1,只有编号为a的物品可供选择时,最优解是什么”,然后下一个小问题是建立在前一个小问题的基础上“当我的背包承重M=1,有编号为a,b的物品可供选择时,最优解是什么”,以此类推。

    2. 为什么能列出状态转移方程?是因为每个状态的最优解,都是根据之前的状态的最优解获得的。具体到背包问题,有以下几点:

      a) 当物品备选情况(物品备选情况指:可供选择的物品的集合)一致时,背包容量M越大,那么sum_v一定大于等于原来的值。

      b) 背包容量M确定时,可供选择的物品N越多,那么sum_v一定大于等于原来的值。

      c) 由a)和b)可得,sum_v的最大值就是当M和N取到最大值时的sum_v

      c) 从思路上说,01背包问题有两个维度:背包容量M,和供选择物品数N。编程的本质是实现人类解决现实问题的思路。仔细想想,如果不借助计算机,你该如何解决这个问题?答案是,例如考虑M=1时,先考虑a能否放入背包,取得最大值,再考虑a和b能否放入背包(a和b都是备选,最终放入背包的可能是a,可能是b,也可能是ab),这时因此与之前只考虑a的情况相比,多了一个b,所以:

    • 要先判断b能否单独放进背包:
      •        如果不能,那么备选为a,b时最大值,等于备选只有a时的最大值(因为b是放不进背包的)。
      •   如果能,即b能够放进去,还有两种可能(即将b放进背包,和不将b放进背包),对这两种可能性,要取最大值:
          •   最终将b放进去(注:此时物品a是否被放进背包是未知的,原因是:剩余的背包容量可能不足以放进物品a,即要在剩余可选物品里找出最优解。
          •   最终没有将b放进去(因为后面可能有比b更合适的物品放进去),此时最大值等于备选只有a时的最大值

      用数学的方式描述上段话:sum_v[i][j]表示将前i件物品列为备选,背包容量为j时,能获得的最大价值;w[i]表示第i件物品的重量,v[i]表示第i件物品的价值

    #此时背包容量 M=1
    if
    1 >=w[2]: sum_v[2][1] = max(sum_v[1][j-w[2]] + v[2], sum_v[1][1]) else: sum_v[2][1] = sum_v[1][1]

      推广到任意情况,即得到我们的状态转移方程

    if j >=w[i]:
        sum_v[i][j] = max(sum_v[i-1][j-w[i]] + v[i], sum_v[i-1][j])
    else:
        sum_v[i][j] = sum_v[i-1][j]

      sum_v的最大值就是sum_v[i][j]的最后一个元素

    如何读图: 例如填充红色格子这里,指在容量M=3,将a,b,c,d 这4件物品考虑在内时,可以取得的最大价值。

    3. 计算时进行简单的数据结构改造。因为当i=1时,即计算开始阶段,还要考虑到如果第1件物品放不进去的情况,此时没有物品在背包中,因此重量和价值都是0.因此需要在表示物品重量和价值的列表前加一个数据0。

    另外,当没有物品在背包中时,价值为0.所以需要sum_v[i][j]初始值全部设为0.

    下面是详细代码:

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    import copy
    
    class ZOPACK(object):
        def __init__(self,n,m,w,v):
            self.num = n
            self.capacity = m
            self.weight_list = [0,] + w
            self.value_list = [0,] + v
            self.Sum_Value_Metrix = self.__CreateMetrix__(self.num+1,self.capacity+1,0)
            
        def __CreateMetrix__(self,x,y,init_value):
            d2_list = []
            for i in range(x):
                d1_list = []
                for j in range(y):
                    d1_list.append(init_value)
                d2_list.append(d1_list)
            return d2_list
            
        def dp(self):
            sum_v = self.Sum_Value_Metrix
            num = self.num
            capacity = self.capacity
            w = self.weight_list
            v = self.value_list
            for i in range(1,num+1):
                for j in range(1,capacity+1):
                    if j >=w[i]:
                        #print("i,j:%s,%s" % (i,j))
                        sum_v[i][j] = max(sum_v[i-1][j-w[i]] + v[i], sum_v[i-1][j])
                    else:
                        sum_v[i][j] = sum_v[i-1][j]
            print("The max value we can get is: ", sum_v[-1][-1])
            print(sum_v)
    
    if __name__ == "__main__":
        num = 5
        capacity = 10
        weight_list = [2, 2, 6, 5, 4]
        value_list = [6, 3, 5, 4, 6]
        q = ZOPACK(num,capacity,weight_list,value_list)
        q.dp()
  • 相关阅读:
    C# Dev PropertyGrid
    C# PropertyGrid控件应用心得
    FileWriter不覆盖
    FileWriter
    java试题
    Java线程池
    java自带线程池和队列详细讲解
    HashMap练习题
    Map集合
    java指定
  • 原文地址:https://www.cnblogs.com/ArsenalfanInECNU/p/8945548.html
Copyright © 2020-2023  润新知