1.0-1揹包問題
有N件物品和一個能裝質量爲W的揹包。第i件物品的重量是w[i],價值是v[i]。求解將哪些物品裝入揹包可使這些物品的總重不超過揹包總重,且價值總和最大。這個問題的特點是:每種物品只有一件,可以選擇放或者不放。
定義f[i][j]:在前i個物品中用容量爲j的包選擇所能得到的最大價值。則轉移方程:f[i][j]=max{f[i-1][j],f[i-1][j-w[i]]+v[i]},v[i]和w[i]對應爲第i件物品的價值和重量。
代碼,對於int[] w={2,2,6,5,4};int[] v={6,3,5,4,6};注意,求得的最優解爲,小於等於揹包容量所能獲得的最高價值
import java.util.Iterator;
import java.util.Vector;
public class bag {//揹包
int maxWeight;
Vector<thing> things;//存儲所選擇的物品
int maxValue;
bag(){this(0);}
bag(int m){
maxWeight=m;
things=new Vector<thing>();
maxValue=0;
}
public void choose(thing[] t){//選擇物品
if(t.length==0||maxWeight==0)
System.out.println("Invalid things array");
int[][] func=new int[t.length+1][maxWeight+1];
int i,j;
for(i=0;i<=maxWeight;i++)
func[0][i]=0;
//最大價值計算
for(i=1;i<=t.length;i++)
for(j=maxWeight;j>0;j--){
if(t[i-1].weight<=j&&(func[i-1][j-t[i-1].weight]+t[i-1].value)>func[i-1][j])
func[i][j]=func[i-1][j-t[i-1].weight]+t[i-1].value;
else
func[i][j]=func[i-1][j];
}
//找出所選擇的物品
j=0;
for(i=1;i<=maxWeight;i++)
if(func[t.length][i]>maxValue)
j=i;
i=t.length;
while(i>0&j>0){
if(func[i][j]!=func[i-1][j]){
things.addElement(t[i-1]);
j-=t[i-1].weight;
}
i--;
}
}
public void show(){//輸出物品
Iterator<thing> iter=things.iterator();
while(iter.hasNext())
System.out.println(iter.next());
}
}
class thing{//物品
int weight;
int value;
thing(){this(0,0);}
thing(int w,int v){weight=w;value=v;}
public String toString(){
return "物品"+Integer.toString(weight)+":"+Integer.toString(value);
}
}
注意:1.由於狀態數作爲矩陣標號,因此要求總重量或者容積必須爲整數。
2.不需要找出所選擇的物品時。可以將空間複雜度由O(NW)將至O(W),即滿足條件更新即可。
3.算法的時間複雜度爲O(NW),對應物品總數和最大重量
4.由於涉及狀態轉移,因此從後往前更新最好
2.完全揹包
完全揹包(CompletePack): 有N種物品和一個容量爲V的揹包,每種物品都有無限件可用。第i種物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。
注意到完全揹包使用了正序更新。因爲每一件物品是無限的。所以在考慮“加選一件第i種物品”這種策略時,卻正需要一個可能已選入第i種物品的子結果。
僞代碼如下。其中空間愛你複雜度爲O(v)時間複雜度爲
for (int i = 1;i <= N;i++)
{
for (int v = 1;v <= V;v++)
{
f[i][v] = 0;
int nCount = v / weight[i];
for (int k = 0;k <= nCount;k++)
{
f[i][v] = max(f[i][v],f[i - 1][v - k * weight[i]] + k * Value[i]);
}
}
}
完全揹包問題有一個很簡單有效的優化,是這樣的:若兩件物品i、j滿足c[i]<=c[j]且w[i]>=w[j],則將物品j去掉,不用考慮。即,如果一個物品A是佔的地少且價值高,而物品B是佔地多,但是價值不怎麼高,那麼肯定是優先考慮A物品的。
3.0-1揹包問題的變種
(1)設報考第i個學校需要花費v[i]美元,能夠被錄用的概率爲w[i]。已知有一定的錢M,則應該如何花錢使得沒有被錄取的概率最小。
設f[i][j]:在前i個學校中花費j元沒有被錄取的概率。狀態初始化爲1。
則轉移方程f[i][j]=min{f[i-1][j],f[i-1][j-v[i]]*(1-w[i])}
(2)設銀行i有現今v[i],小偷全部偷走時,被抓住的概率爲p[i]。已知小偷最多想偷一定的錢,但希望以最大概率逃走。則應該去偷哪些銀行。
f[i][j]表示在前i個銀行中偷得的money爲j時能夠逃脫的最大概率。狀態初始化爲0。
狀態轉移方程:f[i][j]=max{f[i-1][j],f[i-1][j-v[i]]*(1-p[i])}
(3)假設完全裝滿問題下的最優解,以0-1揹包爲例,要求最後的解的重量和爲給定的重量。注意此時,除了0位置之外,初始化均爲最小值。原因1)對於承重爲j的揹包,f[0][j]無任何意義,即無法裝滿、
2)對於第一次f[i][j]不爲負值的情況,要求當前所添加的物體直接把當前揹包填滿。此時用到了最開頭的f[i][0]。而後不爲負值的f[i][j],j=weight[i]+weight[pre].
#include <iostream>
#include <vector>
using namespace std;
const int MIN=0x80000000;
const int N=3; //物品數量
const int V=5; //揹包容量
int f[V+1];
int Package(int *W,int *C,int N,int V);
void main(int argc,char *argv[])
{
int W[4]={0,7,5,8}; //物品權重
int C[4]={0,2,3,4}; //物品大小
int result=Package(W,C,N,V);
if(result>0)
{
cout<<endl;
cout<<"the opt value:"<<result<<endl;
}
else
cout<<"can not find the opt value"<<endl;
return;
}
int Package(int *W,int *C,int N,int V)
{
int i,j;
memset(f,0,sizeof(f)); //初始化爲0
for(i=1;i<=V;i++) //此步驟是解決是否恰好滿足揹包容量,
f[i]=MIN; //若“恰好”滿足揹包容量,即正好裝滿揹包,則加上此步驟,若不需要“恰好”,則初始化爲0
for(i=1;i<=N;i++)
for(j=V;j>=C[i];j--) //注意此處與解法一是順序不同的,弄清原因
{
f[j]=(f[j]>f[j-C[i]]+W[i])?f[j]:(f[j-C[i]]+W[i]);
cout<<"f["<<i<<"]["<<j<<"]="<<f[j]<<endl;
}
return f[V];
}