動態規劃 揹包問題的另一種通俗理解(C )

2021-10-10 15:24:55 字數 3975 閱讀 4850

動態規劃的入門可以先看這篇文章:動態規劃入門

揹包問題是動態規劃中最經典的乙個問題,要是比較難的乙個問題。

有乙個揹包,它的容量為c(capacity),現在有n種不同的物品,編號為0…n-1,其中每一件物品的重量為w(i),價值為v(i)。問可以向這個揹包中盛放哪些物品,使得在不超過揹包容量的基礎上,物品的總價值最大。

變數: c(容量) i(第i個物品) w(i) (第i件物品的重量) v(i)(第i件物品的價值)

要求:(1) w(0)+w(1)+…w(i-1)<=c

(2)v(0)+v(1)+…v(i-1)價值最大【 (2) v(0)+v(1)+…v(i-1)> v(0)+v(1)+…v(i-2)(錯誤)

因為i代表是第i個物品,而第乙個物品的編號是0,所以注意最後下表要減一

思想理解:

注意:題目中說讓物品的總價值最大,並不是說盡最大可能把揹包裝滿同時不超過最大容量就行,而是盡可能挑選出那些高價效比(低容量**值)的優先裝。為了使揹包裡的價值最大,有些物品需要放進去,有些物品不需要放進去,因為有些物品可能會因為重量大價值又小使得價效比太低,比如揹包容量是5,有5個價值為10重量為1的物品,還有乙個價值為1容量為5的物品,如果選擇了將後者放到揹包裡,就放不進去其他物品,導致最終的價值極低。

所以,狀態轉移方程設成了這個樣子:

理解這個方**是費了老半天勁了!

這裡起初有很多疑問:

i個物品放進容量為c的揹包怎麼可能等於i-1個物品放進容量為c的揹包?

答:i代表是總共i個物品。當最後的第i個物品放不進去的時候,i個物品的價值就等於i-1個物品的價值。

如果是裝不下了那第二個等於後面公式就是能裝下的,能裝下為什麼是那樣?

對,第二個不是能裝下,而是必須裝這第i個物品,裝了之後再看剩下的空間裝剩下的i-1個物品。

後期這兩個等式進行比大小有什麼意義?很明顯只要容量夠後面一定大於前面啊?

有意義,兩者比較的是,不裝第i個物品的價值和必須裝第i個物品然後加上從剩下的空間裡得出的最大價值。不裝第i個物品的總數量i-1和裝第i個物品之後的總數量i-1不是同乙個東西。

有個大佬這麼跟我解釋:

這麼分類的思想是利用遞迴,自頂向下的角度來看這個問題,也就是說,第n次放進去的物品可以影響第n-1次,但是第n-1次是不能影響第n次。

但是我還是有點想不通,於是換了乙個思路:

接下來注意聽我說:

不要看截圖裡面的解釋,僅僅作為參考。

我們重新換一種新的理解:

f(n, c)表示從n件物品中,組合出x件(物品數量x可以大於n也可以小於n)價值最大且容量不超過c的物品放到揹包中。

接下來尋找這些變數之間的乙個等價關係。

這個等價關係應該怎樣理解呢?注意聽:

等式左邊 f(i,c)表示從i件物品中挑出容量不小於c的組合的最大價值,它可能等於從i-1件物品中挑出容量不小於c的組合的最大價值,也就是說根本不用從i件物品裡面選最大價值,沒有你加入的最新的i件,同樣可以挑出最大價值組合。

它也有可能等於,首先在最大價值組合裡面,一定要把第i次挑出來的產品放進去。那這樣,這個最大價值組合的其他元素只能在i-1件物品裡面挑選容量之和不大於c-w(i)的物品,因為第i次挑選物品的確定,導致影響了在i-1件物品的挑選,所以出現f(i-1,c-w(i)),在i-1件物品中挑選出容量不大於c-w(i)的物品。

右邊的兩種情況,非常非常巧妙的利用了挑選物品一定算第i次挑選的物品和一定不算第i次挑選的物品這對互斥關係。最後從這兩種組合中選出價值最大的那乙個作為從i件物品中挑出的組大價值。

#include

#include

#include

using

namespace std;

/// 時間複雜度: o(n * c) 其中n為物品個數; c為揹包容積

/// 空間複雜度: o(n * c)

class

knapsack01

public

://從這裡開始看

//w代表重量集合,v是對應的價值集合,c是容量

intknapsack01

(const vector<

int>

&w,const vector<

int>

&v,int c)

};

毫無疑問,遞迴是一定會有重複的,因為計算包含第i次的最大價值和不包含第i次的最大價值的過程一定會重複計算一些次的最大價值,所以出現記憶化遞迴。

遞迴到記憶化遞迴有三步:

1.將遞迴函式轉換為輔助函式(這道題已經是就不用轉換了),新建全域性容器,並在主函式裡面初始化大小(該題特殊之處在於初始化為二維陣列)。

2.在輔助函式中新增if條件語句,考慮當陣列容器為-1時的情況。

3.將遞迴等價關係取得的值記憶到陣列容器中去,最後返回容器。

這個二維陣列空間應該開多大,行數就是物品的數量,列數就是容量+1的大小。

表裡面的列代表第0-0個物品放置,第二列代表0-1兩個物品的放置,第三列代表0-2這三個物品的放置。

/// 揹包問題

/// 記憶化搜尋

/// 時間複雜度: o(n * c) 其中n為物品個數; c為揹包容積

/// 空間複雜度: o(n * c)

class

knapsack01

public

:int

knapsack01

(const vector<

int>

&w,const vector<

int>

&v,int c)

};記憶化遞迴到動態規劃總結:

1.又合併只有乙個函式,對陣列容器原地進行建立並初始化。

2.把遞迴的結束條件轉換為陣列容器的值。

3.利用遍歷和陣列容器中已存在的值,找到陣列容器中的等價關係(注意等價關係的容器陣列下標不再含有n),最後返回所需要的容器值。

注意:1.一定要搞清楚陣列容器中儲存的是什麼!

2.陣列容器的等價關係,一定要搞清楚誰利用誰,才能確定是從前往後還是從後往前遍歷!

這個就是自底向上的進行儲存了

#include

#include

#include

using

namespace std;

/// 揹包問題

/// 動態規劃

/// 時間複雜度: o(n * c) 其中n為物品個數; c為揹包容積

/// 空間複雜度: o(n * c)

class

knapsack01

return memo[n -1]

[c];}}

;

#include

#include

#include

using

namespace std;

/// 揹包問題

/// 動態規劃

/// 時間複雜度: o(n * c) 其中n為物品個數; c為揹包容積

/// 空間複雜度: o(n * c)

class

knapsack01

return memo[

(n -1)

%2][c];}

};

另一種階乘問題

大家都知道階乘這個概念,舉個簡單的例子 5!1 2 3 4 5.現在我們引入一種新的階乘概念,將原來的每個數相乘變為i不大於n的所有奇數相乘例如 5 1 3 5.現在明白現在這種階乘的意思了吧!現在你的任務是求出1 2 n 的正確值 n 20 輸入 第一行輸入乙個a a 20 代表共有a組測試資料 ...

另一種階乘問題

描述 大家都知道階乘這個概念,舉個簡單的例子 5!1 2 3 4 5.現在我們引入一種新的階乘概念,將原來的每個數相乘變為i不大於n的所有奇數相乘例如 5 1 3 5.現在明白現在這種階乘的意思了吧!現在你的任務是求出1 2 n 的正確值 n 20 輸入 第一行輸入乙個a a 20 代表共有a組測試...

另一種階乘問題

時間限制 3000 ms 記憶體限制 65535 kb 難度 1 描述 大家都知道階乘這個概念,舉個簡單的例子 5!1 2 3 4 5.現在我們引入一種新的階乘概念,將原來的每個數相乘變為i不大於n的所有奇數相乘例如 5 1 3 5.現在明白現在這種階乘的意思了吧!現在你的任務是求出1 2 n 的正...