o-1揹包問題

原文:http://blog.163.com/elite_lcf/blog/static/67839538200682625842256/

今天算法課又講到了0-1揹包問題,老師建議是給出的"動態規劃"算法,對每一個揹包,只有兩種可能,放進揹包或者不,用0表示不放,1表示放進揹包.然後在找出她的最優子結構,得出遞歸關係式,再進行計算.我覺得很複雜啦,下面是我的想法.
先決定每件物品的"性價比"!就是用其價值除以總量,得到一組數值,然後從這一組數值中選出當前未選的剩下的並且總量不超過揹包容量的物品放進去,這在當前狀態下是一種最優的結構,接下來找出第二的且總量能相容的物品放進去,依次下去,直到書包完全放滿爲止.

源代碼我會稍後給出.我覺得這樣的算法還是比較好一點.雖然不一定能夠得到最優解.

附註:
謝謝大家不斷指正偶在一個月前犯的小錯!這個想法確實是不成熟的,應該用動態規劃或者回朔來做!
稀裏糊塗這篇文章到了百度搜索的首項,呵呵。。。。。

  01揹包解析和程序

  [問題描述] 
  在M件物品取出若干件放在空間爲W的揹包裏,每件物品的體積爲W1,W·2……Wn,與之相對應的價值爲P1,P2……Pn。求出獲得最大價值的方案。 
  注意:在本題中,所有的體積值均爲整數。 
  [算法分析]: 
  對於揹包問題,通常的處理方法是搜索。 
  用遞歸來完成搜索,算法設計如下: 
  function Make( i {處理到第i件物品} , j{剩餘的空間爲j}:integer) :integer; 
  初始時i=m , j=揹包總容量 
  begin 
  if i:=0 then 
  Make:=0; 
  if j<=wi then (揹包剩餘空間可以放下物品 i ) 
  r1:=Make(i-1,j-wi)+v; (第i件物品放入所能得到的價值 ) 
  r2:=Make(i-1,j) (第i件物品不放所能得到的價值 ) 
  Make:=max{r1,r2} 
  end; 
  這個算法的時間複雜度是O(2^n),我們可以做一些簡單的優化。 
  由於本題中的所有物品的體積均爲整數,經過幾次的選擇後背包的剩餘空間可能會相等,在搜索中會重複計算這些結點,所以,如果我們把搜索過程中計算過的結點的值記錄下來,以保證不重複計算的話,速度就會提高很多。這是簡單?quot;以空間換時間"。 
  我們發現,由於這些計算過程中會出現重疊的結點,符合動態規劃中子問題重疊的性質。 
  同時,可以看出如果通過第N次選擇得到的是一個最優解的話,那麼第N-1次選擇的結果一定也是一個最優解。這符合動態規劃中最優子問題的性質。 
  考慮用動態規劃的方法來解決,這裏的: 
  階段是:在前N件物品中,選取若干件物品放入揹包中; 
  狀態是:在前N件物品中,選取若干件物品放入所剩空間爲W的揹包中的所能獲得的最大價值; 
  決策是:第N件物品放或者不放; 
  由此可以寫出動態轉移方程: 
  我們用f[i,j]表示在前 i 件物品中選擇若干件放在所剩空間爲 j 的揹包裏所能獲得的最大價值 
  f[i,j]=max{f[i-1,j-Wi]+Pi (j<=Wi), f[i-1,j]} 
  這樣,我們可以自底向上地得出在前M件物品中取出若干件放進揹包能獲得的最大價值,也就是f[m,w] 
  算法設計如下:
  procedure Make; 
  begin 
  for i:=0 to w do 
  f[0,i]:=0; 
  for i:=1 to m do 
  for j:=0 to w do begin 
  f[i,j]:=f[i-1,j]; 
  if (j<=w) and (f[i-1,j-w]+v<f[i,j]) then 
  f[i,j]:=f[i-1,j-w]+v; 
  end; 
  writeln(f[m,wt]); 
  end; 
  由於是用了一個二重循環,這個算法的時間複雜度是O(n*w)。而用搜索的時候,當出現最壞的情況,也就是所有的結點都沒有重疊,那麼它的時間複雜度是O(2^n)。看上去前者要快很多。但是,可以發現在搜索中計算過的結點在動態規劃中也全都要計算,而且這裏算得更多(有一些在最後沒有派上用場的結點我們也必須計算),在這一點上好像是矛盾的。 
  事實上,由於我們定下的前提是:所有的結點都沒有重疊。也就是說,任意N件物品的重量相加都不能相等,而所有物品的重量又都是整數,那末這個時候W的最小值是:1+2+2^2+2^3+……+2^n-1=2^n -1 
  此時n*w<2^n,動態規劃比搜索還要慢~~|||||||所以,其實揹包的總容量W和重疊的結點的個數是有關的。 
  考慮能不能不計算那些多餘的結點…… 
  題目
  有N件物品和一個容量爲V的揹包。第i件物品的費用是c,價值是w。求解將哪些物品裝入揹包可使價值總和最大。
  基本思路
  這是最基礎的揹包問題,特點是:每種物品僅有一件,可以選擇放或不放。
  用子問題定義狀態:即f[v]表示前i件物品恰放入一個容量爲v的揹包可以獲得的最大價值。則其狀態轉移方程便是:
  f[v]=max{f[v],f[v-c]+w}
  這個方程非常重要,基本上所有跟揹包相關的問題的方程都是由它衍生出來的。所以有必要將它詳細解釋一下:“將前i件物品放入容量爲v的揹包中”這個子問題,若只考慮第i件物品的策略(放或不放),那麼就可以轉化爲一個只牽扯前i-1件物品的問題。如果不放第i件物品,那麼問題就轉化爲“前i-1件物品放入容量爲v的揹包中”,價值爲f[v];如果放第i件物品,那麼問題就轉化爲“前i-1件物品放入剩下的容量爲v-c的揹包中”,此時能獲得的最大價值就是f[v-c]再加上通過放入第i件物品獲得的價值w。
  優化空間複雜度
  以上方法的時間和空間複雜度均爲O(N*V),其中時間複雜度基本已經不能再優化了,但空間複雜度卻可以優化到O(V)。
  先考慮上面講的基本思路如何實現,肯定是有一個主循環i=1..N,每次算出來二維數組f[0..V]的所有值。那麼,如果只用一個數組f[0..V],能不能保證第i次循環結束後f[v]中表示的就是我們定義的狀態f[v]呢?f[v]是由f[v]和f[v-c]兩個子問題遞推而來,能否保證在推f[v]時(也即在第i次主循環中推f[v]時)能夠得到f[v]和f[v-c]的值呢?事實上,這要求在每次主循環中我們以v=V..0的順序推f[v],這樣才能保證推f[v]時f[v-c]保存的是狀態f[v-c]的值。僞代碼如下:
  for i=1..N
  for v=V..0
  f[v]=max{f[v],f[v-c]+w};
  其中的f[v]=max{f[v],f[v-c]}一句恰就相當於我們的轉移方程f[v]=max{f[v],f[v-c]},因爲現在的f[v-c]就相當於原來的f[v-c]。如果將v的循環順序從上面的逆序改成順序的話,那麼則成了f[v]由f[v-c]推知,與本題意不符,但它卻是另一個重要的揹包問題P02最簡捷的解決方案,故學習只用一維數組解01揹包問題是十分必要的。
  事實上,使用一維數組解01揹包的程序在後面會被多次用到,所以這裏抽象出一個處理一件01揹包中的物品過程,以後的代碼中直接調用不加說明。
  過程ZeroOnePack,表示處理一件01揹包中的物品,兩個參數cost、weight分別表明這件物品的費用和價值。
  procedure ZeroOnePack(cost,weight)
  for v=V..cost
  f[v]=max{f[v],f[v-cost]+weight}
  注意這個過程裏的處理與前面給出的僞代碼有所不同。前面的示例程序寫成v=V..0是爲了在程序中體現每個狀態都按照方程求解了,避免不必要的思維複雜度。而這裏既然已經抽象成看作黑箱的過程了,就可以加入優化。費用爲cost的物品不會影響狀態f[0..cost-1],這是顯然的。
  有了這個過程以後,01揹包問題的僞代碼就可以這樣寫:
  for i=1..N
  ZeroOnePack(c,w);
  初始化的細節問題
  我們看到的求最優解的揹包問題題目中,事實上有兩種不太相同的問法。有的題目要求“恰好裝滿揹包”時的最優解,有的題目則並沒有要求必須把揹包裝滿。一種區別這兩種問法的實現方法是在初始化的時候有所不同。
  如果是第一種問法,要求恰好裝滿揹包,那麼在初始化時除了f[0]爲0其它f[1..V]均設爲-∞,這樣就可以保證最終得到的f[N]是一種恰好裝滿揹包的最優解。
  如果並沒有要求必須把揹包裝滿,而是隻希望價格儘量大,初始化時應該將f[0..V]全部設爲0。
  爲什麼呢?可以這樣理解:初始化的f數組事實上就是在沒有任何物品可以放入揹包時的合法狀態。如果要求揹包恰好裝滿,那麼此時只有容量爲0的揹包可能被價值爲0的nothing“恰好裝滿”,其它容量的揹包均沒有合法的解,屬於未定義的狀態,它們的值就都應該是-∞了。如果揹包並非必須被裝滿,那麼任何容量的揹包都有一個合法解“什麼都不裝”,這個解的價值爲0,所以初始時狀態的值也就全部爲0了。
  這個小技巧完全可以推廣到其它類型的揹包問題,後面也就不再對進行狀態轉移之前的初始化進行講解。
  小結
  01揹包問題是最基本的揹包問題,它包含了揹包問題中設計狀態、方程的最基本思想,另外,別的類型的揹包問題往往也可以轉換成01揹包問題求解。故一定要仔細體會上面基本思路的得出方法,狀態轉移方程的意義,以及最後怎樣優化的空間複雜度。
  例題一個旅行者有最多能裝M公斤的揹包,現有N件物品,它們的重量分別爲W1,W2,W3,...Wn,它們的價值分別爲C1,C2,C3...Cn。求旅行者應選哪幾種物品裝入揹包,使包內物品的總價值最大。
  第一行:兩個整數,m(揹包容量,m《=200)和n(物品數量,n《=30) 
  第2行。。。n+1行:每行兩個整數wi和ci,表示每個物品的重量和價值
  僅一行,一個數,表示最大總價值
  Input:
  10 4
  2 1
  3 3
  4 5
  7 9
  Output:
  12
  var n,v,i,j:longint;
  f:array[0..200]of longint;
  w,c:array[1..30]of longint;
  begin
  while not eof do begin
  read(v,n);
  for i:=1 to n do read(w,c);
  fillchar(f,sizeof(f),0);
  for i:=1 to n do
  for j:=v downto w do
  if f[j]<f[j-w]+c then
  f[j]:=f[j-w]+c;
  writeln(f[v]);
  end;
  end.
  裝箱問題
  有一個箱子容量爲V(正整數,0≤V≤20000),同時有n個物品(0小於n≤30),每個物品有一個體積(正整數)。要求從n個物品中,任取若干個裝入箱內,使箱子的剩餘空間爲最小。
  輸入v,n,在輸入n個物品。
  輸出箱子的剩餘空間爲最小。
  Input:
  24 一個整數,表示箱子容量 
  6 一個整數,表示有n個物品 
  8 接下來n行,分別表示這n個物品的各自體積。 
  3 
  12 
  7 
  9 
  7
  Output:
  0 一個整數,表示箱子剩餘空間。
  var v,n,i,j,k:longint;
  f:array[0..20000]of boolean;
  a:array[1..30]of longint;
  begin
  while not eof do begin
  read(v,n);
  for i:=1 to n do read(a);
  f[0]:=true;
  for i:=1 to n do
  for j:=v downto a do
  if not f[j] and f[j-a] then
  f[j]:=true;
  k:=v;
  while (k<1)and(not f[k]) do dec(k);
  writeln(v-k);
  end;
  end.


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