大前端算法篇之背包问题

简述:背包问题是动态规划算法中的一个经典问题,分为01背包和完全背包,01背包就是不能放入同一件物品,完全背包是可以放入同一个物品

下面将要讲的是01背包问题

动态规划中最重要的是先分析思路,然后总结出规律,最后得出一个公式

案例:假设有一个背包大前端算法篇之背包问题_第1张图片,可以放入单位重量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

对表格的解读:

  1. v[r][0]  = 0 ;v[0][c] = 0;表示在背包容量为0,或者没有物品时可能放入的最大价值就是0
  2. 如果当前要放入的物品大于背包的容量,那就直接取上一次背包的最大价值,也就是 w[r] > c 那么 v[r][c] = v[r-1][c]
  3. 如果当前要放入的物品小于等于背包的容量,那就取 当前放入的价值加上剩余空间可以放入的最大价值不放入当前物品时的价值 两者之间取最大值,也就是 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()

 

你可能感兴趣的:(大前端算法篇之背包问题)