01背包问题
有 n
件物品和一个最多能背重量为 w
的背包。第 i
件物品的重量是 weight[i]
,得到的价值是 value[i]
。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
此处最简单的方式就是使用回溯法去暴力求解:每一个物品有且仅有两种状态,放入背包或不放入背包,时间复杂度
使用动态规划进行求解,削减时间复杂度:此处的重点依旧在于问题的划分上。
动态规划的过程
首先使用经典的动态规划方式进行问题的划分:
- 动态规划数组的定义:
dp[i]
表示最多能背重量为i
的背包能够装的物品的价值总和。 - 动态规划数组的推导:当
weight[j]<i
时,dp[i]=max(dp[i-weight[j]]+value[j])
其中j=0..n
- 动态规划数组的初始化:此处在进行数组的初始化时,就会出现问题,并且这样的处理还存在一个问题,无法处理小数的情况(通常是整数)
使用二维的动态规划数组进行处理:
动态规划数组的定义:
dp[i][j]
表示从下标为[0-i]
的物品里任意取,放进容量为j
的背包,价值总和最大是多少。动态规划数组的推导:对于
dp[i][j]
相较于上一行dp[i-1][j]
背包的容量是相同的,但是可选的物品多了一个i
,那么要么尝试选择物品i
(若是weight[i]<=j
)就有dp[i][j]=value[i]+dp[i-1][j-weight[i]]
,要么不选取物品i
那么依旧是从前i-1
个物品中进行选取,那么dp[i][j]=dp[i-1][j]
,在上述两种情况中取最大值即可。那么就有dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])
动态规划数组的初始化:对于第一列
dp[i][0]
容量为0,那么全部初始化为0,对于第一行dp[0][j]
,在weight[0]>j
时全部初始化为0,当weight[0]<=j
时,全部初始化为value[0]
动态规划数组的遍历方式:在计算
dp[i][j]
时,将会使用到前行的信息,所以采取从左向右逐行遍历的方式。(实际上在计算dp[i][j]
所使用到的前面的dp[a][b]
的信息,此处的a<i&&b<j
所以,采取逐列遍历的方式也是可以的。 )举例验证:对于
vector<int> weight={1,3,4}; vector<int> value={15,20,30};
情形,按照上述方式计算得到的值为35,验证正确。
代码实现:
1 |
|
使用滚动数组优化空间复杂度
此处在进行 dp[i][j]
数组的计算时,实际上只需要使用到前一行中 dp[i-1][j]
和 dp[i-1][j-weight[i]]
的信息,因此可以使用一个一维数组来实现。
使用滚动数组的实现方式:想要计算第 i+1
行的信息,将第 i
行作为第 i+1
行的初始值,并且此处在计算 dp[i][j]
时还需要使用到 dp[i-1][j-weight[i]]
的信息,在使用滚动数组的前提下也就是需要使用 dp[i][j-weight[i]]
的信息,那么在行内的遍历方式就需要从右向左进行。从实际问题的角度出发去理解:若是采取从左向右的便利方式,那么就会出现物品被重复放入的问题,在第 i
次迭代中, dp[k]
将物品 i
放入一次,计算得到新的 dp[k]
,在 dp[k+1]
时可能再次放入 i
并且使用 dp[k]
去更新 dp[k+1]
这就出现了重复放置的问题。
动态规划数组的定义:初始
dp[j]
表示,在物品0
中选取,容量为j
的背包能够得到的最大价值,在迭代i
次后,dp[j]
表示在物品0-i
中选取,容量为j
的背包能够得到的最大价值。dp.size()=w+1
动态规划数组的递推:对于
dp[j]
在第i
次迭代时,dp[j]=max(dp[j],dp[j-weight[i]]+value[i]) if j>=weiht[i] else dp[j]
动态规划数组的初始化:依旧是
dp[i]=value[0] if i>=weight[0] else 0
动态规划数组的遍历顺序:首先是对从前向后对物品进行遍历
i=1,...,n-1
,再从右向左对背包进行遍历j=w,...,1
(此处背包为0的情况直接省略,无须遍历)举例验证:对于
vector<int> weight={1,3,4}; vector<int> value={15,20,30};
情形,按照上述方式计算得到的值为35,验证正确。
代码实现:
在上述的遍历过程可以进行进一步的改写,对于 j
的取值范围,当 j<weight[i]
时在继续遍历实际上就没有意义了,此时 dp[j]=dp[j]
即可。因此 j
的范围是 w,...,weight[i]
1 |
|