简述:背包问题是动态规划算法中的一个经典问题,分为01背包和完全背包,01背包就是不能放入同一件物品,完全背包是可以放入同一个物品
下面将要讲的是01背包问题
动态规划中最重要的是先分析思路,然后总结出规律,最后得出一个公式
案例:假设有一个背包,可以放入单位重量5的物品,然后我们有三个物品
物品编号/属性 | 物品1 | 物品2 | 物品3 |
单位重量 | 1 | 3 | 4 |
价值 | 2 | 4 | 5 |
如果在不超过背包最大承受重量的情况下如何放入物品,能够使得背包内物品的价值最大化。
对于这种问题,我们可以先填一个表格,我们用r(row)表示行,c(col)表示列,w[r]表示第r个物品的重量,c就是背包的容量,v[r][c]表示前r个物品可以放入当前背包重量c的最大价值,singleV(r) 代表第r个物品的价值
背包容积(单位重量)/物品编号 | 0 | 1 | 2 | 3 | 4 | 5 |
没有物品 | 0 | 0 | 0 | 0 | 0 | 0 |
物品1 | 0 | 2 | 2 | 2 | 2 | 2 |
物品2 | 0 | 2 | 2 | 4 | 6 | 6 |
物品3 | 0 | 2 | 2 | 4 | 6 | 7 |
对表格的解读:
- v[r][0] = 0 ;v[0][c] = 0;表示在背包容量为0,或者没有物品时可能放入的最大价值就是0
- 如果当前要放入的物品大于背包的容量,那就直接取上一次背包的最大价值,也就是 w[r] > c 那么 v[r][c] = v[r-1][c]
- 如果当前要放入的物品小于等于背包的容量,那就取 当前放入的价值加上剩余空间可以放入的最大价值 和 不放入当前物品时的价值 两者之间取最大值,也就是 w[r] <= c 那么 v[r][c] = max(v[r-1][c], singleV(r) + v[r-1][c-w[r])
我们可以随便拿表格中的最右下角那个值来验证一下,根据我们上面的推断:
此时 r = 3, w[r] = 4 , c = 5 符号第3点规则 ,v[3][5] = max(v[3-1][5], singleV(3) + v[3-1][5-4]) = max(6,5 + 2) = 7 整合就是表格中的值
接下来用代码写一下这个算法
// 背包问题算法
function knapsackProblem() {
let itemWeights = [1, 3, 4] // 物品的重量
let itemValus = [2, 4, 5] // 物品的价值
let weight = 5 // 背包的容积
let nums = itemWeights.length // 物品的个数
// 代表放入最大价值的二维数组
let v = Array.from({ length: nums + 1 }).map(item => Array.from({ length: weight + 1 }))
// 打印输出一下上面的表格
for (let r = 0; r < v.length; r++) {
for (let c = 0; c < v[0].length; c++) {
v[r][c] = 0
}
}
// console.log(v)
// 这时输出的全是0 ,因为我们没有判断是否可以放入物品
/**
* [
[ 0, 0, 0, 0, 0, 0 ],
[ 0, 0, 0, 0, 0, 0 ],
[ 0, 0, 0, 0, 0, 0 ],
[ 0, 0, 0, 0, 0, 0 ]
]
*/
// 接下来根据我们上面的规则放入物品,因为第一行和第一列全是0,所以我们行和列从1开始
for (let r = 1; r < v.length; r++) {
for (let c = 1; c < v[0].length; c++) {
if (itemWeights[r - 1] > c) {
v[r][c] = v[r - 1][c]
} else {
v[r][c] = Math.max(v[r - 1][c], itemValus[r - 1] + v[r - 1][c - itemWeights[r - 1]])
}
}
}
console.log(v)
/**
* 这里我们就得到了上面的表格,这里我们可以看出7就是能够放入的最大价值,
* 但此时我们还不知道里面放入了那些物品,所以我们还得记录放入的物品
* [
[ 0, 0, 0, 0, 0, 0 ],
[ 0, 2, 2, 2, 2, 2 ],
[ 0, 2, 2, 4, 6, 6 ],
[ 0, 2, 2, 4, 6, 7 ]
]
*/
// 用来记录
let records = Array.from({ length: nums + 1 }).map(item => Array.from({ length: weight + 1 }))
for (let r = 1; r < v.length; r++) {
for (let c = 1; c < v[0].length; c++) {
if (itemWeights[r - 1] > c) {
v[r][c] = v[r - 1][c]
} else {
// v[r][c] = Math.max(v[r - 1][c], itemValus[r - 1] + v[r - 1][c - itemWeights[r - 1]])
if (v[r - 1][c] < itemValus[r - 1] + v[r - 1][c - itemWeights[r - 1]]) {
v[r][c] = itemValus[r - 1] + v[r - 1][c - itemWeights[r - 1]]
// 只在此记录,因为这里是已经把当前物品放入了
records[r][c] = true
} else {
v[r][c] = v[r - 1][c]
}
}
}
}
// for (let i = 0; i < records.length; i++) {
// for (let j = 0; j < records[0].length; j++) {
// if (records[i][j]) {
// console.log(`第${i}个物品放入背包`)
// }
// }
// }
/**
* 通过上面的打印我们还是不知道具体是哪些物品放入了背包,
* 我们可以从后往前遍历
* 第1个物品放入背包
第1个物品放入背包
第1个物品放入背包
第1个物品放入背包
第1个物品放入背包
第2个物品放入背包
第2个物品放入背包
第2个物品放入背包
第3个物品放入背包
*/
let i = records.length - 1
let j = records[0].length - 1
while (i > 0) {
if (records[i][j]) {
console.log(`第${i}个物品放入背包`)
j -= itemWeights[i - 1]
}
i--
}
/**
* 这里我们就知道哪些物品放入背包了
* 第3个物品放入背包
第1个物品放入背包
*/
}
knapsackProblem()