這段時間天天加班,確實沒有整塊的時間來寫博客了,一不當心就到週末了,要是不寫篇博客,那就又要鴿了。爲了避免打臉,仍是加班加點的把這篇博客給寫了出來。java
再說個題外話,最近一直在看一本關於Mysql
的掘金小冊,感受很棒,做者用通俗易懂的語言將Mysql
的底層原理進行了介紹,圖文並茂,講解的很深刻,能夠看出做者應該是花了很多心思,借閱了很多書籍的。聽說做者是個95後,爲了寫這本小冊子還特地辭了職,簡直優秀!sql
一篇文章大概須要花費40~60分鐘,建議花整塊的時間進行閱讀。數組
從做者的身上,也看到了一種匠心精神,反觀本身,寫這麼水的文章,實在是慚愧。因此決定對文章質量把把關,本着寧缺毋濫的原則來寫做,儘可能不浪費你們的時間。優化
好了,閒話就說到這了,言歸正傳。code
上一篇中,咱們瞭解了01揹包問題
,並用三種方法進行了求解,但其實在最後一種解法上,咱們還能再對它的空間複雜度進行優化。blog
已通過去一個星期了,可能一部分人已經忘記了以前的解題思路,因此在這裏把以前填表法使用到的圖貼了過來:遞歸
這是咱們上一篇填表法的最終結果,在這裏,聰明的你應該能發現,其實這裏大部分的內容都沒有用上,那麼讓咱們來想一想,如何優化一下空間複雜度呢?博客
再回頭看下以前的遞推關係式:io
能夠發現,每次求解 KS(i,j)
只與KS(i-1,m) {m:1...j}
有關。也就是說,若是咱們知道了K(i-1,1...j)
就確定能求出KS(i,j)
,爲了更直觀的理解,再畫一張圖:class
下一層只須要根據上一層的結果便可推出答案,舉個栗子,看i=3,j=5
時,在求這個子問題的最優解時,根據上述推導公式,KS(3,5) = max{KS(2,5)
,KS(2,0) + 3} = max{6,3} = 6
;若是咱們獲得了i=2
時全部子問題的解,那麼就很容易求出i=3
時全部子問題的解。
所以,咱們能夠將求解空間進行優化,將二維數組壓縮成一維數組,此時,裝填轉移方程變爲:
KS(j) = max{KS(j),KS(j - wi) + vi}
這裏KS(j - wi)
就至關於原來的KS(i-1, j - wi)
。須要注意的是,因爲KS(j)
是由它前面的KS(m){m:1..j}
推導出來的,因此在第二輪循環掃描的時候應該由後往前進行計算,由於若是由前日後推導的話,前一次循環保存下來的值可能會被修改,從而形成錯誤。
這麼說也許仍是不太清楚,回頭看上面的圖,咱們從i=2
推算i=3
的子問題的解時,此時一維數組中存放的是{0,0,2,4,4,6,6,6,6,6,6}
,這是i=2
時全部子問題的解,若是咱們從前日後推算i=3
時的解,好比,咱們計算KS(0) = 0,KS(1) = KS(1) = 0
(由於j=1時,裝不下第三個珠寶,第三個珠寶的重量爲5),KS(2) = 2,KS(3) = 4,KS(4) = 4, KS(5) = max{KS(5), KS(5-5) + 3} = 6,....,KS(8) = max{KS(8),KS(8 - 5) + 3} = 7
。在這裏計算KS(8)的時候,咱們就把原來KS(8)的內容修改掉了,這樣,咱們後續計算就沒法找到這個位置的原值(這個栗子沒舉好。。由於後面的計算沒有用到KS(8)= =),也就是上一輪循環中計算出來的值了,因此在遍歷的時候,須要從後往前進行倒序遍歷。
public class Solution{ int[] vs = {0,2,4,3,7}; int[] ws = {0,2,3,5,5}; int[] newResults = new int[11]; @Test public void test() { int result = ksp(4,10); System.out.println(result); } private int ksp(int i, int c){ // 開始填表 for (int m = 0; m < vs.length; m++){ int w = ws[m]; int v = vs[m]; for (int n = c; n >= w; n--){ newResults[n] = Math.max(newResults[n] , newResults[n - w] + v); } // 能夠在這裏輸出中間結果 System.out.println(JSON.toJSONString(newResults)); } return newResults[newResults.length - 1]; } }
輸出以下:
[0,0,0,0,0,0,0,0,0,0,0] [0,0,2,2,2,2,2,2,2,2,2] [0,0,2,4,4,6,6,6,6,6,6] [0,0,2,4,4,6,6,6,7,7,9] [0,0,2,4,4,7,7,9,11,11,13] 13
這樣,咱們就順利將空間複雜度從O(n*c)
優化到了O(c)
。固然,空間優化的代價是,咱們只能知道最終的結果,但沒法再回溯中間的選擇,也就是沒法根據最終結果來找到咱們要選的物品組合。
01揹包問題
通常有兩種不一樣的問法,一種是「剛好裝滿揹包」
的最優解,要求揹包必須裝滿,那麼在初始化的時候,除了KS(0)
爲0
,其餘的KS(j)
都應該設置爲負無窮大
,這樣就能夠保證最終獲得的KS(c)
是剛好裝滿揹包的最優解。另外一種問法不要求裝滿
,而是隻但願最終獲得的價值儘量大
,那麼初始化的時候,應該將KS(0...c)
所有設置爲0
。
爲何呢?由於初始化的數組,其實是在沒有任何物品能夠放入揹包的狀況下的合法狀態
。若是要求揹包剛好裝滿,那麼此時只有容量爲0的揹包能夠在什麼都不裝且價值爲0的狀況下被「剛好裝滿」
,其餘容量的揹包均沒有合法的解,所以屬於未定義的狀態,應該設置爲負無窮大
。若是揹包不須要被裝滿,那麼任何容量的揹包都有合法解,那就是「什麼都不裝」。這個解的價值爲0,因此初始狀態的值都是0。
01揹包問題
能夠用自上而下
的遞歸記憶法
求解,也能夠用自下而上
的填表法
求解,然後者能夠將二維數組的解空間優化成一維數組的解空間,從而實現空間複雜度的優化。
對於01揹包問題
的兩種不一樣問法,實際上的區別即是初始值
設置不同,解題思路是同樣的。
關於01揹包問題
,介紹到這裏就已經所有結束了,但願能對你們有所幫助。若是以爲有收穫,不要吝嗇你的贊哦,也歡迎關注個人公衆號留言交流。