每天一道算法題(34)——揹包問題

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];
}




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