動態規劃-經典問題(0-1揹包問題)分析及優化

目錄

1.0-1揹包問題的分析

(1)狀態方程

2.遞歸算法

3.記憶化搜索

4.動態規劃

5.優化1——空間複雜度O(2C) 

6.優化2——空間複雜度O(C) 

7.0-1揹包問題的變種


 如上圖是一個LeetCode的經典問題,0-1揹包問題

1.0-1揹包問題的分析

嘗試下面的算法

暴力解法:每一件物品都可以放進揹包,也可以不放進揹包,時間複雜度爲O((2^n)*n),需要耗費太久的時間     X

貪心算法:優先放入平均價值最高的物品,如下圖例子                                                                                           X

假設有一個容量爲5的揹包

(1)此時如果採用貪心算法,則應該是先放入6,佔了一個容量,再放入10,佔了2個容量,此時一共佔用了3個容量,無法繼續放入第3個物品,此時貪心算法的結果就是16

(2) 但如果我們不放入1,只放入2,3物品,則此時揹包容量剛好填滿,價值爲22。此時我們剛好放棄了平均價值最大的物品,

因此貪心算法是不正確的

(1)狀態方程

F(n,C):考慮將n個物品放進容量爲C的揹包,使得價值最大聲

狀態轉移方程分析:

狀態有兩種,一種是該物品放進揹包,一種是不放進揹包,直接考慮後面的物品,兩種狀態取大值即可

F(i,C) = F(i-1,C)                          不放進揹包,直接考慮後面的物品

                  =v(i) + F(i-1,C-w(i))該物品放進揹包

狀態轉移方程:F(i,C)= max(F(i-1,C),v(i) + F(i-1,C-w(i)))

2.遞歸算法

class Solution {
    private $w,$v;  //使其成爲成員變量
    public function knapsack01($w,$v,$c){
        $len = count($w);
        $this->w = $w;
        $this->v = $v;
        return $this->bestValue($len-1,$c);
    }
    /**
     * [用[0...index]的物品,填充容積爲c的揹包的最大價值]
     * @param  [type] $index [考慮到的物品的下標]
     * @param  [type] $c     [剩餘的容量]
     */
    private function bestValue($index,$c){
        if($index < 0 || $c <= 0) return 0;
        $res = $this->bestValue($index-1,$c);       //不放入該物體,直接考慮後面的物品放入
        if($c >= $this->w[$index])                  //如果該物體能放得下該揹包,則放入,並與上面的策略取大值
            $res = max($res, $this->v[$index] + $this->bestValue($index-1,$c-$this->w[$index]));
        return $res;
    }
}
$q = new Solution();
$w = [1,2,3];
$v = [6,10,12];
$c = 5;
var_dump($q->knapsack01($w,$v,$c));

3.記憶化搜索

遞歸解答中存在大量的重疊子結構問題,可以利用index 和 剩餘容量 c 作爲記憶化數組的下標

class Solution {
    private $w,$v;       //使其成爲成員變量
    private $memo = [];  //初始化記憶化數組
    public function knapsack01($w,$v,$c){
        $len = count($w);
        $this->w = $w;
        $this->v = $v;
        return $this->bestValue($len-1,$c);
    }
    /**
     * [用[0...index]的物品,填充容積爲c的揹包的最大價值]
     * @param  [type] $index [考慮到的物品的下標]
     * @param  [type] $c     [剩餘的容量]
     */
    private function bestValue($index,$c){
        if($index < 0 || $c <= 0) return 0;
        if(isset($this->memo[$index][$c]))          //檢索是否已經檢索過
            return $this->memo[$index][$c];
        $res = $this->bestValue($index-1,$c);       //不放入該物體,直接考慮後面的物品放入
        if($c >= $this->w[$index])                  //如果該物體能放得下該揹包,則放入,並與上面的策略取大值
            $res = max($res, $this->v[$index] + $this->bestValue($index-1,$c-$this->w[$index]));
        $this->memo[$index][$c] = $res;             //保存記憶化數組
        return $res;
    }
}
$q = new Solution();
$w = [1,2,3];
$v = [6,10,12];
$c = 5;
var_dump($q->knapsack01($w,$v,$c));

4.動態規劃

在二維數組中使用動態規劃,模擬填充過程.

行爲物品,列爲容量

(1)填充第一行,容量爲0的時候,不能填充,因此該點的價值爲0。容量爲1的時候,可以填充物品0,此時價值爲6,往後的所有點的最大價值都爲6 

 (2)填充第二行,每一個元素點都有兩種可能,一種爲放入該物品,另一種爲不放入該物品

  • 0點,無法放入物體,最大價值依舊是0;下標1,無法放入1號物品,因此最大價值爲放入1號下標之前的最大收益,爲6
  • 下標2,可以放入物品1,因此有兩種情況,不放入該物品時,所獲最大價值爲該容量的上一個最大收益,爲6;放入該物體時,價值則等於該物體的價值10加上當前容量減去該物體所佔體積的(2-2)的容量位置的最大收益的值,即爲10+0 = 10 > 6,則下標2位置的最大收益變爲 10
  • 以此類推,一直到容量5,不放入該物體時,當前容量的最大收益爲6;放入該物體時,10 + 6 = 16 > 6 ,因此該下標的最大收益爲16

(3)以此類推,填充第三行,到容積爲3時才能放入該物體,到最後一個下標5時,如果不放入該物體,則最大收益爲上一行容量的最大收益;如果放入該物體,則最大收益爲,該物體所獲收益12 + 容量-該物體佔用容量的容量下標所獲最大收益10 10+12=22>16,因此最終答案爲22

class Solution {
    public function knapsack01($w,$v,$c){
        $len = count($w);                        //求數組長度
        $dp = [];                                //初始化動態規劃二維數組
        for($j = 0; $j <= $c; ++$j)              //初始化第一行數據
            $dp[0][$j] = $j >= $w[0]? $v[0]: 0;
        for($i = 1;$i < $len; ++$i){             //從第二行開始冬天規劃
            for($j = 0;$j <= $c; ++$j){
                $dp[$i][$j] = $dp[$i-1][$j];     //不放入物品的策略
                if($j >= $w[$i])                 //如果物品可以放入,則取與物品可以放入的最大值
                    $dp[$i][$j] = max($dp[$i][$j], $v[$i] + $dp[$i-1][$j-$w[$i]]);
            }
        }
        return $dp[$len-1][$c];                  //最終,最後一個元素未所求答案
    }
}
$q = new Solution();
$w = [1,2,3];
$v = [6,10,12];
$c = 5;
var_dump($q->knapsack01($w,$v,$c));

5.優化1——空間複雜度O(2C) 

原本的動態規劃的時間複雜度爲O(n*c) ,空間複雜度爲O(n*c)

狀態轉移方程:F(i,C)= max(F(i-1,C),v(i) + F(i-1,C-w(i)))

第i行元素只依賴於第i-1行元素,所以理論上,只需要保持兩行元素即可,空間複雜度O(2*c) = O(c)

我們可以定義兩行,第一行都在處理偶數的數,第二行都是奇數

通過節省空間,可以解決的問題範圍就大大增加了

class Solution {
    public function knapsack01($w,$v,$c){
        $len = count($w);                         //求數組長度
        $dp = [];                                 //初始化動態規劃二維數組
        for($j = 0; $j <= $c; ++$j)               //初始化第一行數據
            $dp[0][$j] = $j >= $w[0]? $v[0]: 0; 
        for($i = 1;$i < $len; ++$i){              //從第二行開始冬天規劃
            for($j = 0;$j <= $c; ++$j){ 
                $dp[$i%2][$j] = $dp[($i-1)%2][$j];//不放入物品的策略
                if($j >= $w[$i])                  //如果物品可以放入,則取與物品可以放入的最大值
                    $dp[$i%2][$j] = max($dp[$i%2][$j], $v[$i] + $dp[($i-1)%2][$j-$w[$i]]);
            }
        }
        return $dp[($len-1)%2][$c];               //最終,最後一個元素未所求答案
    }
}
$q = new Solution();
$w = [1,2,3];
$v = [6,10,12];
$c = 5;
var_dump($q->knapsack01($w,$v,$c));

6.優化2——空間複雜度O(C) 

根據上面的圖例,每一次更新都會參考上面和左邊的內容,右邊的內容不會進行操作

因此我們可以只開闢一個一維的長度爲C的數組,從右往左進行動態規劃

不僅可以節省時間複雜度,$j 只需遍歷到比當前物體佔用位置大的下標,再小就放不下去

還可以節省空間複雜度,O(C),每次都與自身(即不放入當前物體)和放入物體後取大值

簡潔代碼

class Solution {
    public function knapsack01($w,$v,$c){
        $len = count($w);                        //求數組長度
        $dp = [];                                //初始化動態規劃二維數組
        for($j = 0; $j <= $c; ++$j)              //初始化第一行數據
            $dp[$j] = $j >= $w[0]? $v[0]: 0;
        for($i = 1;$i < $len; ++$i){             //從第二個物品開始,從右往左開始動態規劃
            for($j = $c;$j >= $w[$i]; --$j){     //$j爲當前的容量,當前的容量要當前物品所佔用的地方,否則放不下去
                $dp[$j] = max($dp[$j], $v[$i] + $dp[$j-$w[$i]]);//跟原先的自己(即不放入該物體)和放入該物體後比較
            }
        }
        return $dp[$c];                  //最終,最後一個元素爲所求答案
    }
}
$q = new Solution();
$w = [1,2,3];
$v = [6,10,12];
$c = 5;
var_dump($q->knapsack01($w,$v,$c));      //22

7.0-1揹包問題的變種

完全揹包問題:每個物品可以無限使用

多重揹包問題:每個物品不止1個,有num(i)個

多維費用揹包問題:要同時考慮物品的體積和重量兩個維度

物品之間互相排斥/互相依賴

……各種約束,腦瓜疼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章