引言
算法的经典问题之一背包问题,背包问题是动态规划(运筹学)的一个典型的例子,它的问题描述即规定背包所能容纳的最大重量,然后此时有一批物品对应不同的质量和价值,那么如何放置物品进入背包使得背包中的价值最大。
通常情况下,背包问题会分为两种情形:
- 物品可以切开放置到背包中,即物品的重量和价值可以按分数的形式放置
- 物品只能以整数的形式放置
前一种情况,只能用贪心算法解决去解决问题。后一种情况,则需要通过动态规划。
背包问题(整数)
首先,我们先来认识一下动态规划解决问题的步骤:
- 定义子问题
- 实现要反复执行来解决子问题的部分
- 识别并求解出基线条件
然后假设此时我们有一个这样的背包问题:
此时有一个背包可容纳的重量为 7,分别有重量为 2、3、4 对应价值为 3、4、5 的三个物品 ,并分别命名为物品 1、物品 2、物品 3。要如何往背包中放置物品,才能使得背包中的价值最高?
对于从事编程的同学而言,遇到略显复杂的问题,我们应该先建立数学模型。而对于这个背包问题,它的数学模型是这样的:
背包所能容纳的重量 w:5
物品 重量 价值
1 2 3
2 3 4
3 4 5
(矩阵的行数为物品数量+1,列为背包所能容纳重量+1,初始化默认 0 列 0 行为 0)
第一阶段
i/w 0 1 2 3 4 5
0 0 0 0 0 0 0
1 0 0 3 3 3 3
2 0
3 0
第二阶段
i/w 0 1 2 3 4 5
0 0 0 0 0 0 0
1 0 0 3 3 3 3
2 0 0 3 4 4 7
3 0 0 3 4 5 7(最高价值)
可以看出对于我们这个背包问题,只需要两个阶段就可以放入背包的物品最高价值。但是,我们还需要找出对应价值的物品(这个过程就不建模了,比较简单,直接看代码)。
/*
capacity 背包所能容纳的最大重量
n 物品的数量
kS 最优方案对应的矩阵
weights 物品对应的重量数组
values 物品对应的价值数组
*/
function findGoods(n, capacity, kS, weights, values) {
let i = n
let k = capacity
while(i > 0 && k > 0) {
debugger
if (kS[i][k] !== kS[i - 1][k]) {
console.log(`物品${i}可以是解的一部分, 重量:${weights[i - 1]}, 价值:${values[i - 1]}`)
i--
k -= kS[i][k]
} else {
i--
}
}
}
既然,我们已经写好背包问题的数学模型,以及准备好寻找对应价值的物品的函数。接下来,就把背包问题对应数学模型的算法实现:
/*
capacity 背包所能容纳的最大重量
weights 物品对应的重量数组
values 物品对应的价值数组
n 物品的数量
*/
function knapSack(capacity, weights, values, n) {
// 初始化用于寻找解决方案的矩阵
const kS = []
for (let i = 0; i <= n; i++) {
kS[i] = []
}
for (let i = 0; i <= n; i++) {
for (let w = 0; w <= capacity; w++) {
// 令矩阵中索引值为0的项值为0
if (i === 0 || w === 0) {
kS[i][w] = 0
} else if (weights[i - 1] <= w) { // 物品的重量需要小于等于约束条件
const a = values[i - 1] + kS[i - 1][w - weights[i - 1]]
const b = kS[i - 1][w]
kS[i][w] = a > b ? a: b
} else { // 超出背包的重量
// 使用之前的值
kS[i][w] = kS[i - 1][w]
}
}
}
findValues(n, capacity, kS, weights, values)
return kS[n][capacity]
}
这个算法其实非常简单,正如我们前面描述的数学模型一样,矩阵中满足约束条件 weights[i - 1] <= w 的进行相应的计算,然后对比矩阵此时索引中上一行对应列的值,谁大此时对应索引值就为谁。反正,如果不满足约束条件,就取矩阵此时索引中上一行对应列的值。
背包问题(分数)
对于背包问题的分数版,动态规划就无能为力了。这个时候,就需要用贪心算法解决,先来看看贪心算法的定义。贪心算法是一种近似解决问题的技术,通过每个阶段的局部最优解,从而达到全局最优解的效果。简单地理解,就是类似于归纳法推出符合大多数情况下公式,然后通过这个公式求解。
function knapSack(capacity, weights, values) {
const n = values.length
let load = 0
let val = 0
for (let i = 0; i < n && load < capacity; i++) {
if (weights[i] <= capacity - load) {
val += values[i]
load += weights[i]
} else {
const r = (capacity -load) / weights[i]
val += r * values[i]
load += weights[i]
}
return val
}
}
对比动态规划的背包问题,贪心算法不仅贪心(简洁)而且更易理解,只要我此时背包剩余的空间能容纳下这个物体我就放进去,不能那我就放这个物体的一部分。
来源:CSDN
作者:WJCHumble
链接:https://blog.csdn.net/qq_42049445/article/details/104451798