0-1背包问题python_01背包问题-史上最详细最解答

01背包问题-史上最详细最解答

1. 题目

1.1 题目

有 N 件物品和一个容量是 V的背包。每件物品只能使用一次。

第 i件物品的重量是 w[i],价值是v[i]。

求解:

将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

1.2 输入输出

输出:输出最大价值。

输入:

第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N行,每行两个整数 w[i],v[i],用空格隔开,分别表示第 i件物品的重量和价值。

输出格式:输出一个整数,表示最大价值。

1.3 数据范围

0

0

1.4 输入输出样例

输入样例:

4 5

1 2

2 4

3 4

4 5

输出样例:

8

2. 分析

2.1 状态表示

一般用dp数组来计算动态规划问题,从以下两个方面对动态规划问题进行表示

集合

v集合:物品价值

w集合:物品重量

从前i个物品里面选取总重量<=j的所有物品的选法

属性

max

min

count

本题属性是属于求最大价值,为max

2.2 状态计算

对于0和1背包的问题,我们计算的只是两个状态,即对于第i个物品选择放进去或者不放进去的问题。

选择放进去

表示在上一个物品的状态的时候,我的当前背包重量j需要减去当前这个物品的重量w[i],并且整个背包的价值需要加上当前这个物品的价值v[i],则状态方程为:

dp[i][j] = dp[i-1][j-w[i]] + v[i]

选择不放进去

实际上如果选择不放进去的时候,表示需要减去的w[i]和需要加上的v[i]都为0选择不放进去的状态方程则为:

# dp[i][j] = dp[i-1][j-0] + 0

dp[i][j] = dp[i-1][j]

由此我们可以得到状态转移方程:

dp[i][j] = max(dp[i-1][j-w[i]] + v[i], dp[i-1][j])

3. 实现

根据上面的状态转移方程我们可以得到01背包的二维解法:

def bag_two_2_0and1(items, weight):

# 数据是从1开始的

data_len = len(items)

row = data_len + 1

col = weight + 1

# 生成二维数组

# 原生的Python可以这么写:

# dp = [[0] * col for _ in range(row)]

# 使用numpy生成一个dp数组

dp = np.array([0] * (row * col)).reshape(row, col)

for i in range(1, row):

if i == data_len:

break

item = items[i]

v = item.get("value")

w = item.get("weight")

for j in range(1, col):

if j >= w:

no_input = dp[i-1][j]

yes_input = dp[i-1][j-w] + v

dp[i][j] = max(yes_input, no_input)

else:

dp[i][j] = dp[i-1][j]

return dp[data_len-1][weight]

4. 优化

可以看的出来i这个变量其实就是表示“第i个”的一个递增序列,实际的这个背包的当前的状态只有重量(w)和价值(v)

根据刚才的状态方程:

# 不放进去

dp[i][j]=dp[i-1][j]

# 放进去

dp[i][j]=dp[i-1][j-w[i]] + v[i]

观察两个状态方程,可以看到对于背包重量的状态j与i无关,因此可以把上述方程简化为:

# 不放进去时候,重量不变,价值不变

dp[j] = dp[j]

# 放进去的时候,背包重量和价值的变化

dp[j] = dp[j-w[i]] + v[i]

因此可以得到状态转移方程为:

dp[j] = max(dp[j-w[i]] + v[i], dp[j])

根据上述的状态转移方程来实现代码:

def bag_one_dim_0and1(items, weight):

# 数据是从1开始的

data_len = len(items)

row = data_len + 1

col = weight + 1

# 生成一维数组

# 原生的Python可以这么写:

# dp = [[0] * col]

# 使用numpy生成一个dp数组

dp = np.array([0] * col)

for i in range(1, row):

if i == data_len:

break

item = items[i]

v = item.get("value")

w = item.get("weight")

for j in range(weight, w, -1):

dp[j] = max(dp[j-w]+v, dp[j])

return dp[weight]

【1】此处为何倒序遍历呢?

首先我们观察优化后和优化前的状态转移方程:

# 优化之前

dp[i][j] = max(dp[i-1][j-w[i]] + v[i], dp[i-1][j])

# 优化之后

dp[j] = max(dp[j-w[i]] + v[i], dp[j])

因此实际上优化后的状态转移方程是:

dp[j](第i轮的新值) = max(dp[j-w[i]] + v[i](第i-1轮的旧值), dp[j](第i-1轮的旧值))

优化后的状态转移方程实际上就是用最新的值把上一轮的值覆盖掉,所以可以在一个一维数组中完成状态转移,而且得保证:这一轮状态只能是由上一轮的状态推出来的。

为什么需要逆序遍历,此处如果从背包问题的物理操作去解释不好解释,简单的从数学上去理解就是:

我们这个j-w[i]是做减法的,而这个j又是数组的下标,做减法之后就表示是之前的数据。由于需要用新的值把旧的值进行覆盖,就需要保证在此数据是没有被改动过的,也就是原封不动第i-1轮的数据与当前第i轮的数据进行比较。因此此处如果是顺序的话,这个数据就已经是第i轮更新的数据与第i轮的数据进行比较了。

5. 测试

我们给出01背包的测试数据

{

'items': [{

'number': 1,

'weight': 49,

'value': 241

}, {

'number': 1,

'weight': 25,

'value': 724

}, {

'number': 1,

'weight': 91,

'value': 780

}, {

'number': 1,

'weight': 76,

'value': 824

}, {

'number': 1,

'weight': 92,

'value': 968

}, {

'number': 1,

'weight': 53,

'value': 276

}, {

'number': 1,

'weight': 6,

'value': 492

}, {

'number': 1,

'weight': 53,

'value': 745

}, {

'number': 1,

'weight': 62,

'value': 136

}, {

'number': 1,

'weight': 94,

'value': 568

}],

'total_weight': 200,

'things_num': 10

}

输出:

3008

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值