從算法看揹包問題(1)

從算法看揹包問題(1)

揹包問題(Knapsack problem)是一種組合優化的NP徹底問題。問題能夠描述爲:給定一組物品,每種物品都有本身的重量和價格,在限定的總重量內,咱們如何選擇,才能使得物品的總價格最高。問題的名稱來源於如何選擇最合適的物品放置於給定揹包中。類似問題常常出如今商業、組合數學,計算複雜性理論、密碼學和應用數學等領域中。javascript

N件物品和一個容量爲C的揹包。第i件物品的重量(即體積,下同)是W[i],價值是V[i]。求解將哪些物品裝入揹包可以使這些物品的費用總和不超過揹包容量,且價值總和最大。java

簡化一下吧,一個最大重量爲5的揹包,有以下物件:算法

物品 重量 價值
0 2 3
1 3 4
2 4 5

請問應如何選取,能使揹包價值最大?後端

建表

在作這題以前,應該創建一個表。優化

把這個表填滿,就是用程序去實現算法的過程。this


先決條件

令對應格子的可以存放的最大價值爲$f(i,j)$,code

第一條重要原則,是解決問題的先決條件blog

空間能給你放,你就放。

轉化爲數學語言就是:繼承

第一行分析

先來看第一行,只考慮物品1:ip

  • 考慮容量C[j],j爲0,1時,物品0毫無疑問放不下。

    ∴ f(2,j)=0;

  • C爲5時,能夠放下。因此填充的價值爲3.f(0,2)=v[0]=2.

  • ...

第一行,要麼放不下(爲0,j<w[i]),要麼放進去(v[i],此時j>=w[i]).

由此:

此時填充的表格爲:

第二行分析

接着看第二行。

  • 考慮容量C[j],j爲0,1,2時,物品1毫無疑問放不下。f(1,j)繼承f(0,j)的值。

  • j爲3時,由於w[0]+w[1]>j,所以只存在個選擇:要麼放物品0,要麼放物品1.放和不放要經過比較來決定。

    若是不放物品0,那麼這個值爲4

    若是決定物品0,那麼容量此時變成了j-w[0],那麼去找f(1,j-w[0])這個格子。顯然f(1,j-w[0])==f(1,3)==0,咱們要取大的那個,因此只放物品1爲最優解。填入4。

  • j=5時。兩個都放得下。因此f(1,5)=7

經過比較得知:此時填充的表格爲:

好了,揹包問題的算法實際上能夠結束了。

多餘的第三行分析:概括現象

爲了作完整,最後再看第三行:

  • j=0,1,2,3(j<w[2])時,根本放不下物品2,不予考慮,此時:f(2,j)繼承f(1,j)的值。

    $f(2,j)=f(1,j)$。

  • 當j=4也就是w[2]<=j時,能夠放下物品2,此時須要比較

    先看f(1,4),它佔用了w[1]=3的空間,此時空間爲j-3,找到f(2,j-3)==f(2,1)==0

    若是直接放物品2.結果爲5,所以填上5。

  • 最後看C5。仍是比較:f(1,5)的方案是放物品0和1,佔用爲2+3=5,此時空間還剩下5-5=0,去檢索f(2,0)得0.

    所以,取最大填上7。

算到最後,你會發現,問題的解答在於表格右下角,也就是所有的最大值。也就是7.

同時你也會發現第二第三行實際上是同樣的。

所以

算法實現

假如後端給你的數據以下:

let table = [{
    good: '雞蛋',
    weight: 2,
    value: 3
}, {
    good: '西紅柿',
    weight: 3,
    value: 4
}, {
    good: '茄子',
    weight: 4,
    value: 5
}]

還須要處理下。

class Knapsack {
    constructor(table, capacity) {
        // 初始化
        this.nums = table.length - 1;
        this.goods = [];
        this.weights = [];
        this.values = [];
        this.capacity = capacity;
        table.map((x, i) => {
            this.goods.push(x.good);
            this.weights.push(x.weight);
            this.values.push(x.value);
        });

        /**
         * 封裝一個類,放到表格中
         * items爲名目
         *  */
        this.UnitData = function () {
            this.init = function (items, value) {
                return {
                    items, value
                };
            }
        }
    }

    getItems() {
        // 初始化表格
        let table = [[]];
        let { UnitData, capacity, nums, weights, values, goods } = this;

        // 建立列,第一行判斷
        for (let j = 0; j <= capacity; j++) {
            let unitData = new UnitData();
            if (j < this.weights[0]) {
                // 啥也放不了
                table[0][j] = unitData.init([], 0)
            } else {
                // 容許放第一個商品
                table[0][j] = unitData.init([goods[0]], values[0])
            }
        }

        // 第二行開始判斷
        for (let j = 0; j <= capacity; j++) {
            for (let i = 1; i <= nums; i++) {
                // 建立行
                if (!table[i]) {
                    table[i] = [];
                }
                // 第2個商品起開始比較判斷
                if (j < weights[i]) {
                    // 容量小則繼承
                    table[i][j] = table[i - 1][j];
                } else {
                    //不然比較,查找。
                    let a = table[i - 1][j].value;
                    let b = table[i - 1][j - weights[i]].value + values[i]

                    if (a > b) {
                        table[i][j] = table[i - 1][j];
                    } else {
                        let unitData = new UnitData();
                        // 終於找到了。把物品扔進去!媽了個巴子的
                        table[i - 1][j].items.push(goods[i]);
                        table[i][j] = unitData.init(table[i - 1][j].items, table[i - 1][j - weights[i]].value + values[i])
                    }
                }
            }
        }

        // 返回表格右下角的數據
        return table[nums][capacity]
    }


}

var a = new Knapsack(table, 5)

console.log(a.getItems())
// { items: [ '雞蛋', '西紅柿' ], value: 7 }

由此,基於動態規劃算法的0-1揹包問題的問題解決。

相關文章
相關標籤/搜索