有乙個包含n個元素的集合s,每個元素ei都有乙個對應的權值wi。現在有乙個界限w,我們希望從s中選擇出部分元素,使得這些元素的權值之和在不超過w的情況下達到最大,這個便是子集合問題(事實上還有其他型別的子集和問題,本文暫不討論)。舉個更具體一點的例子,某農民今年收成小番茄總重量為w萬斤,有n個採購商想要向這位農民收購小番茄,他們想要採購的數目都有所不同,採購商i想要收購wi萬斤小番茄(1 <= i <= n)。現在農民需要從採購商中挑選部分買家,將小番茄賣給他們,使得自己被收購的小番茄數目達到最大,從而賺取最大的收入(假設所有採購商給出的單位收購價都是一樣的;所有採購商的收購量總和超過農民的收成量,即農民無法滿足所有採購商;收購商i想要收購wi萬斤小番茄,他不會只收購一半或者更多)。
根據動態規劃的思路,我們來分析一下這個問題的子問題。假設農民已經從前面n-1個收購商中選出了一組最優組合使得收購量最大,現在他要考慮是否要賣給最後乙個收購商n。假如賣給收購商n,那麼他能夠賣給前面n-1個收購商的番茄就只有(w-wn)萬斤。如果他不賣給收購商n,那麼他能夠賣給前面n-1個收購商的番茄就是w萬斤了。於是,假設在只有(w-wn)萬斤的情況下,從前面n-1個收購商中選出最優組合所收購的總重量加上賣給收購商n的重量為(o1+wn)。若w萬斤都賣給前面n-1個收購商,他們之中選出的最優組合所收購的總重量為o2。農民需要考慮的問題就變成了比較(o1+wn)和o2的大小了。
我們更形式化一點地進行描述,假設o(i, w)表示將w萬斤小番茄提供給收購商收購的時候,從這i個收購商中選出最優組合所收購的總重量。那麼o1 = o(n-1, w-wn),o2 = (n-1, w)。當農民考慮收購商n的時候,他需要判定o1和o2的大小。另外一種特殊情況,當收購商收購的數量wn超過農民擁有的所有小番茄的時候,即w < wn,那麼農民自然只能考慮前面n-1個收購商中的最優組合了。
更進一步考慮,當我們考慮o(i, w)的時候,如果w能夠容納wi,那麼我們需要考慮o(i-1, w)和(o(i-1, w-wi) + wi)的大小。如果w無法容納wi,即w < wi,那麼無需考慮i,o(i, w) = o(i-1, w)。因此,我們可以得到遞推公式如下所示:
if w < wi, o(i, w) = o(i-1, w)
else o(i, w) = max(o(i-1, w), wi + o(i-1, w-wi))
得到了遞推公式,我們自然就可以得到乙個演算法來算出最優解。演算法的偽**如下所示:
陣列o[0...n, 0...w]
for w = 0, 1, ..., w
初始化o[0, w] = 0
endfor
for i = 1, 2, ..., n
for w = 0, ..., w
if w < wi
o[i, w] = o[i-1, w]
else
o[i, w] = max(o[i-1, w], wi + o[i-1, w-wi])
endfor
endfor
o[n, w]即為最優解
演算法實際上是實現了乙個填表的過程,填了一張n*w的二維**。整個演算法的時間複雜度為o(nw),顯而易見,當w的值變得很大的時候,這個演算法的效率堪憂。
初始化i = n, w = w
while
i != 0 do
if o[i, w] == o[i-1, w]
i = i-1
else
print i
i = i-1
w = w-wi
endif
endwhile
下面給出乙個簡單的c++**實現,**後面還附帶有簡單的示例。
#include
#include
using
namespace
std;
const
int max_num = 100;
const
int max_weight = 1000;
class subsetproblem
} void process() else
if (optional[i-1][w] < weight[i] + optional[i-1][w-weight[i]]) else }}
} void result() else
}cout
<< "\n[optional] "
<< optional[n][w] << endl;
}private:
int n, w;
int weight[max_num + 1];
int optional[max_num + 1][max_weight + 1];
};int main()
ssp.init(n, w);
ssp.process();
ssp.result();
return
0;}
輸入輸出示例:
362
23[select] 3
1[optional] 5
把上面的子集和問題拓展一下,就變成了我們常見的揹包問題了。問題定義:有乙個包含n個元素的集合s,每個元素ei都有乙個對應的權值wi和乙個對應的價值vi。現在有乙個界限w,我們希望從s中選擇出部分元素,使得這些元素的權值之和在不超過w的情況下,所有元素的價值總和達到最大。繼續拿上面的農民買小番茄的例子來講,假設現在每個收購商的出價都有所不同,收購商i打算收購wi萬斤小番茄,出價vi。農民只有w萬斤能夠提供給收購商,他希望合理選擇收購商,使得賣小番茄的收益達到最大。
這個問題看起來跟前面的子集和問題很像,解決思路也是基本一樣。同樣地,農民在考慮收購商n的時候,他需要考慮兩個子問題。第一,如果把w萬斤小番茄全部拿來提供給前面的n-1個收購商,他能拿到的最大收益為o(n-1, w)。第二,如果先**部分小番茄給收購商n,剩下的再提供給前面n-1個收購商,他能拿到的最大收益為(o(n-1, w-wn) + vn)。於是,只要o(n-1, w)的價值更大,農民自然不會考慮收購商n,否則他肯定要先**部分小番茄給收購商n。最後,考慮特殊情況,當收購商n的收購量超過農民所能提供的小番茄時,農民也就只能將w萬斤小番茄提供給前面n-1個收購商了。
假設o(i, w)表示農民將w萬斤小番茄提供給前i個收購商所能賺取的最大收益,按照之前講解子集和問題的思路,我們可以得到遞推公式如下:
if w < wi, o(i, w) = o(i-1, w)
else o(i, w) = max(o(i-1, w), vi + o(i-1, w-wi))
可以看到,公式幾乎跟之前的子集和問題一樣。只不過,子集和問題中我們考慮的是重量w,而這裡我們考慮的是價值v。兩者本質上是同樣的,只不過判定的標準變了而已。解決這種型別的揹包問題的演算法跟之前的一樣,只需要將wi換成vi即可,最後反向搜尋求解最優解的元素組合情況的做法也是與之前一樣,**相似度較高,所以下面我就不重複貼**了。這個演算法的時間複雜度也是o(nw),反向搜尋的時間複雜度為o(n)。演算法缺陷也是跟之前一樣,當w的值非常大的時候,演算法效率低下。 經典動態規劃 子集揹包問題
416.分割等和子集 上篇文章 經典動態規劃 0 1 揹包問題 詳解了通用的 0 1 揹包問題,今天來看看揹包問題的思想能夠如何運用到其他演算法題目。而且,不是經常有讀者問,怎麼將二維動態規劃壓縮成一維動態規劃嗎?這就是狀態壓縮,很容易的,本文也會提及這種技巧。先看一下題目 演算法的函式簽名如下 輸...
動態規劃 揹包問題
給定n個物品,重量是,價值是,包的容量 承重 是w 問,放入哪些物品能使得包內價值最大 1 需要將問題轉化為子問題,通過遞迴實現,且子問題必然與父問題存在關聯 2 定義v i,j 表示為,當item取自前i個items且揹包capacity j 時,揹包問題的最優解,也即最高的價值。3 從前i個it...
動態規劃 揹包問題
不廢話,直接上 動態規劃,揹包問題。輸入為 int n 物品的種類數。int n weight 各件物品的重量。int n value 各種物品的價值。int w 揹包最大的裝載重量。輸出 v n b 的值,最大的裝載價值。x n 各類物品的裝載數量。author huangyongye publi...