经典算法-01背包问题

824 阅读2分钟

01背包问题是什么?

假设现在有物品[a,b,c,d,e] 5件,
每件物品的重量(KG)不同,分别为[3,4,1,8,4]
每件物品的价值不同,分别为[1,2,3,4,5],

现在小明现在用可以装12KG的背包一个,
想带走最多价值的物品, 请问最多可以带走的价值是多少? 分别带走哪些物品?

注意:每个物品最多只能带一个

思路:贪心算法 不行

最容易想到的思路:

  1. 重量大于背包的不考虑,因为放不下
  2. 重量大价值小的不考虑,因为价值不高
    是不是就挑选 价值/重量 大的就可以了呢?(也即贪心算法,每次选择当前最优)

看个例子:
假设有 5 个物品,N = 5,背包容量为 100
每种物品的质量与价值如下:

  • value/weight = 20/20 = 1
  • value/weight = 30/30 = 1
  • value/weight = 44/40 = 1.1
  • value/weight = 55/50 = 11.1
  • value/weight = 60/60 = 1

如果按上述策略:优先选“价质比”最大的是: (第三个+第四个)
此时质量:40+50=90 价值:44+55 =99

实际更优的选择策略是: (第一个 + 第二个 + 第四个)
此时质量:20+30+50=100 价值:20+30+55=105
虽然1,2高价质比不是最高,但是1,2,4能装满整个背包,也就是最佳组合

=》所以,“价质比”这种贪心策略显然不是最优策略。

原因是:这个问题包含2个层面最优,1是最高价值/重量比,2是最佳组合
因为这个算法仅仅满足高价质比,没有满足最佳组合

思路:动态规划 可以

dp[i][j] 为将前i件物品装进容量为j的背包可以获得的最大价值
dp表为(N+1)x(C+1)维的二维数组 0<=i<=N, 0<=j<=C

状态转移方程

  1. 如果w[i] > j,则背包放不下,dp[i][j] = dp[i-1][j];
  2. 如果w[i] <= j,则第i件物品有2种考虑
    2.1. 不放入背包,此时价值就是i-1物品放入重量为j的背包 dp[i][j] = dp[i-1][j];
    2.2. 如果放入背包,此时价值为i-1物品放入j-w[i]背包的价值 + i物品放入的价值 dp[i][j] = dp[i-1][ j-w[i] ] + v[i];
    2.3. 放还是不放呢?取决于哪种情况的价值更大,也就是 dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i])

注意:

  • 每个物品只有1个,物品i要么不放(0) 要么放(1),也就是01背包问题

  • 这种算法的核心就是: 自底向上,当前值由前面已知值推算出来

状态转移过程图表分析

纵向:表示物品i
横向:表示背包空间j

横向物品为0,也即是没有物品,背包不管空间多大,价值都为0
纵向背包空间为0,也就是背包放不下任何物品,价值都为0
image-20210812145506846

纵向背包空间1
image-20210812151128448

纵向背包空间2
image-20210812152726000

同理推算剩下的纵向背包3-12
image-20210812153510571

思考:背包里放了哪些物品呢?
从i=5,j=12单元格倒退,
dp[5][12] -> dp[4][8](没有放,丢弃) -> dp[3][8] -> dp[2][7] -> dp[1][3]
知道背包里放了物品 4,3,2,1
总价值为5+3+2+1=11

image-20210812153220611

js实现

function packBag(N, C, w, v){
    // 创建二维数组 (N+1) * (C+1)
    let dp = new Array(N+1); // N+1行
    for(let z = 0;z <= N; z++){ // C+1列
        dp[z] = new Array(C+1);    
    }
    // 初始化第0行和第0列初始值为0
    for(let z=0; z<=C; z++){
        dp[0][z] = 0;
    }
    for(let z=0; z<=N; z++){
        dp[z][0] = 0;
    }
    // 计算1~N行到1~C列的数据,从列开始算
    for(let j=1; j<=C; j++){
        for(let i=1;i<=N; i++){
            let notPackValue = dp[i-1][j];
            if(w[i] > j){ // 不能放
                dp[i][j] = notPackValue;                
            }else{
                let packValue = dp[i-1][j-w[i]] + v[i];
                dp[i][j] = Math.max(notPackValue,packValue);
            }
        }
    }
    console.log(`dp:`,dp);
    return dp[N][C];
}
module.exports = {
    packBag : packBag
};

测试数据 {N:5, C:12, w:[0,3,4,1,8,4], v:[0,1,2,3,4,5]}

运行结果

dp: [
  [0,   0,   0,   0,   0,   0,  0,   0,   0,   0,   0,   0,  0 ],  
  [0,   0,   0,   1,   1,   1,  1,   1,   1,   1,   1,   1,  1 ],  
  [0,   0,   0,   1,   2,   2,  2,   3,   3,   3,   3,   3,  3 ],  
  [0,   3,   3,   3,   4,   5,  5,   5,   6,   6,   6,   6,  6 ],  
  [0,   3,   3,   3,   4,   5,  5,   5,   6,   7,   7,   7,  8 ],  
  [0,   3,   3,   3,   5,   8,  8,   8,   9,   10,  10,  10, 11 ]
]
result:  11