算法(二)——揹包問題

1、ios

P01: 01揹包問題

N件物品和一個容量爲V 的揹包。放入第i件物品耗費的費用是Ci1,獲得的價值是Wi。求解將哪些物品裝入揹包可以使價值總和最大。 算法

價值數組v = {8, 10, 6, 3, 7, 2},數組

重量數組w = {4, 6, 2, 2, 5, 1},函數

揹包容量C = 12。不超過容量的狀況下,使得價值最大。優化

一、spa

#include<iostream>
#include<string.h>
#include<cmath> 
using namespace std;

int main() 
{ 
    int v[7]={0,8,10,6,3,7,2};//價值 ,必定要注意對應關係,多一行,多一列 
    int w[7]={0,4,6,2,2,5,1};//重量 
    int c=12,n=6;//容量,個數 
    int num[6+1][12+1]={0};//預留空間多了一行和一列 

    for(int i=1;i<=n;i++) //num[i][j],i個物品,j爲容量時,的最大價值 
	{
	    for(int j=1;j<=c;j++)
		{
	        if(w[i]<=j)//能夠選擇 ,比較新加的和舊的哪一個大 
			{
				num[i][j]=max(num[i-1][j],num[i-1][j-w[i]]+v[i]); 
		    }
		    else//重量比容量大,不被選擇,維持原來 
		    {
		    	num[i][j]=num[i-1][j];
			}
	    }  	
	}    
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=c;j++)
		{
			cout<<num[i][j]<<" ";
		}
		cout<<endl;
	}
}   

必定要注意空間的聲明,能夠提早聲明一個較大的空間這樣避免空間溢出。指針

以上是矩陣全部數據都保存下來,能夠只保存兩行c列,優化空間blog

二、遞歸

#include<iostream>
#include<string.h>
#include<cmath> 
using namespace std;

int main() 
{ 
    int v[7]={0,8,10,6,3,7,2};//價值 ,必定要注意對應關係,多一行,多一列 
    int w[7]={0,4,6,2,2,5,1};//重量 
    int c=12,n=6;//容量,個數 
    int num[12+1]={0};//預留空間多了一行和一列 
    int tem[12+1]={0};//中間變量 
    
    for(int i=1;i<=n;i++) //num[i][j],i個物品,j爲容量時,的最大價值 
	{
	    for(int j=1;j<=c;j++)
		{
	        if(w[i]<=j)//能夠選擇 ,比較新加的和舊的哪一個大 
			{
				num[j]=max(tem[j],tem[j-w[i]]+v[i]); 
		    }
		    else//重量比容量大,不被選擇,維持原來 
		    {
		    	num[j]=tem[j];
			}
	    }
		for(int k=0;k<13;k++)
		{
			tem[k]=num[k];
		}
	}  
	for(int i=1;i<13;i++)
	{
		cout<<num[i]<<" ";
	}
}   

大大節省空間。ci

三、只保留一行的逆序求解程序;

#include<iostream>
#include<string.h>
#include<cmath> 
using namespace std;

int main() 
{ 
    int v[7]={0,8,10,6,3,7,2};//價值 ,必定要注意對應關係,多一行,多一列 
    int w[7]={0,4,6,2,2,5,1};//重量 
    int c=12,n=6;//容量,個數 
    int num[12+1]={0};//預留空間多了一列 存儲0位置 
    
    for(int i=1;i<=n;i++) //num[i][j],i個物品,j爲容量時,的最大價值 
	{
	    for(int j=c;j>0;j--)//由於須要用到前面的數據,因此逆向求解 
		{
	        if(w[i]<=j)//能夠選擇 ,比較新加的和舊的哪一個大 
			{
				num[j]=max(num[j],num[j-w[i]]+v[i]); 
		    }
	    }
	}  
	for(int i=1;i<13;i++)
	{
		cout<<num[i]<<" ";
	}
}   

四、利用遞歸的方法求解

#include<iostream>
#include<string.h>
#include<cmath> 
using namespace std;

int Getnum(int n,int c)
{
	int v[7]={0,8,10,6,3,7,2};//價值 ,必定要注意對應關係,多一行,多一列 
    int w[7]={0,4,6,2,2,5,1};//重量 
	int num=0;
    if(n==0)//邊界條件 
        num=0;
    else
    {
    	if(c<w[n])
    	{
    		num=Getnum(n-1,c);
		}
		else
		{
			num=max(Getnum(n-1,c),Getnum(n-1,c-w[n])+v[n]);
		}
	}
    return num;	    	
} 

int main() 
{ 
    int c=12,n=6;//容量,個數 
    cout<<Getnum(6,12);
}   

利用這樣的方法,程序簡單,可是會增長時間複雜度。

五、將該問題改成循環輸入

#include<iostream>
#include<string.h>
#include<cmath> 
using namespace std;

const int N=50;
int v[N]={0};
int w[N]={0}; 

int Getnum(int n,int c)
{
	//int v[6]={8,10,6,3,7,2};//價值 ,必定要注意對應關係,多一行,多一列 
    //int w[6]={4,6,2,2,5,1};//重量 
	int num=0;
    if(n==0)//邊界條件 
        num=0;
    else
    {
    	if(c<w[n])
    	{
    		num=Getnum(n-1,c);
		}
		else
		{
			num=max(Getnum(n-1,c),Getnum(n-1,c-w[n])+v[n]);
		}
	}
    return num;	    	
} 

int main() 
{ 
    int c,n;//容量,個數 
    while(cin>>c>>n)
    {
    	for(int i=1;i<=n;i++)
    	{
    		cin>>v[i];
		}
		for(int i=1;i<=n;i++)
    	{
    		cin>>w[i];
		}
		cout<<Getnum(n,c);
		v[N]={0};
		w[N]={0};
	}
   
    
}   

六、

經過指針來傳遞數組

#include<iostream>
#include<cmath>
#include<string.h> 

using namespace std;

int Sum(int n,int *P)//指針來傳遞數組
{
	int s;
	for(int i=0;i<n;i++)
	{
		s+=P[i];
	}
	return s;
}

int main()
{
	int v[5]={0,1,2,3,4};
	cout<<Sum(5,v); 
}

 

2、

P02: 徹底揹包問題

題目

有N種物品和一個容量爲V的揹包,每種物品都有無限件可用。第i種物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可以使這些物品的費用總和不超過揹包容量,且價值總和最大。 

/*"""
徹底揹包問題(每一個物品能夠取無限次)
:param N: 物品個數, 如 N=5
:param V: 揹包總容量, 如V=15
:param weight: 每一個物品的容量數組表示, 如weight=[5,4,7,2,6]
:param value: 每一個物品的價值數組表示, 如value=[12,3,10,3,6]
:return: 返回最大的總價值
"""*/

代碼實現

一、

#include <iostream>  
#include <cstring>  
using namespace std;  
   
const int N=50;  
    
int main()  
{  
    int v[N]={0,12,3,10,3,6};//價值  
    int w[N]={0,5,4,7,2,6}; //重量 
  
  
    int m[N][N];  
    int n=5,c=15; //容量15 
    memset(m,0,sizeof(m)); 
	cout<<m[2][0]<<endl; 
    for(int i=1;i<=n;i++)  
    {  
        for(int j=1;j<=c;j++)  
        {
        	m[i][j]=m[i-1][j];
		    for(int k=1;k<=(j/w[i]);k++)  
		    {
		    	if(m[i-1][j]<m[i-1][j-w[i]*k]+v[i]*k)
				    m[i][j]=m[i-1][j-w[i]*k]+v[i]*k;
			}      
        }  
    }  
  
  
    for(int i=1;i<=n;i++)  
    {  
        for(int j=1;j<=c;j++)  
        {  
            cout<<m[i][j]<<' ';  
        }  
        cout<<endl;  
    }  
  
  
    return 0;  
}  

屢次循環比較要比的不只僅是 m[i-1][j]和m[i-1][j-w[i]*k]+v[i]*k單一的一個數比較。隨着k的變化,實際上是m[i-1][j]、m[i-1][j-w[i]*k]+v[i]*k和新生成的m[i][j]三個數的比較。寫成以下形式的話就會出錯

#include <iostream>  
#include <cstring>  
#include<cmath>
using namespace std;  
   
const int N=50;  
    
int main()  
{  
    int v[N]={0,12,3,10,3,6};//價值  
    int w[N]={0,5,4,7,2,6}; //重量 
  
  
    int m[N][N];  
    int n=5,c=15; //容量15 
    memset(m,0,sizeof(m));  
    for(int i=1;i<=n;i++)  
    {  
        for(int j=1;j<=c;j++)  
        {
		    for(int k=1;k<=(j/w[i]);k++)  
		    {
		    	m[i][j]=max(m[i-1][j],m[i-1][j-w[i]*k]+v[i]*k);//出錯,只比較了兩個數 
			}      
        }  
    }  
  
    for(int i=1;i<=n;i++)  
    {  
        for(int j=1;j<=c;j++)  
        {  
            cout<<m[i][j]<<' ';  
        }  
        cout<<endl;  
    }  
  
  
    return 0;  
}  

以m[2][9]爲例,本應該是15,這裏出現12是因爲,當k=2時,m[i-1][j-w[i]*k]+v[i]*k=m[1][1]+3*2=6,小於m[i-1][j]=12,覆蓋掉了以前的15。這就是因爲比較沒有考慮新生成的數引發的錯誤。

但其實上面的代碼也沒有與新生成的m[i][j]做比較。這是少了覆蓋這一層,結果便正確~  

 改成以下也對

#include <iostream>  
#include <cstring>  
#include<cmath>
using namespace std;  
   
const int N=50;  
    
int main()  
{  
    int v[N]={0,12,3,10,3,6};//價值  
    int w[N]={0,5,4,7,2,6}; //重量 
  
  
    int m[N][N];  
    int n=5,c=15; //容量15 
    memset(m,0,sizeof(m));  
    for(int i=1;i<=n;i++)  
    {  
        for(int j=1;j<=c;j++)  
        {
        	m[i][j]=m[i-1][j];//至關於m[i][j]提早賦了個最大值,而後和自身比較找出最大的便可
for(int k=1;k<=(j/w[i]);k++) { m[i][j]=max(m[i][j],m[i-1][j-w[i]*k]+v[i]*k);// } } } for(int i=1;i<=n;i++) { for(int j=1;j<=c;j++) { cout<<m[i][j]<<' '; } cout<<endl; } return 0; }

以上方法是將徹底揹包轉換爲了01揹包,比較狀態是n個物品和n-1個物品狀態比較,

二、空間的優化

#include<cstdio> 
#include<algorithm> 
#include<iostream>

using namespace std; 
int w[300],c[300],f[300010]={0}; 
int V,n; 
 
int main()
{
    V=15;
	n=5;
	int w[6]={0,12,3,10,3,6};//價值
	int c[6]={0,5,4,7,2,6}; //重量 
    for(int i=1;i<=n;i++)
    {
    	for(int j=c[i];j<=V;j++)//保證j>c[i] 
    	{
    		f[j]=max(f[j-c[i]]+w[i],f[j]);//設 f[v]表示重量不超過v公斤的最大價值,最簡單的方式
		}
	}
	for(int i=1;i<=15;i++)
	    cout<<f[i]<<" ";
    
    return 0; 
}

最簡單的形式,判斷條件和01揹包相比,採用順序,而非逆序,由於比較的是同一件物品從1加到n件的不一樣,而不跨越到上一個物品,而和上一個物品比較則根據下標來比的,簡化了好多。 

三、遞歸實現

#include<iostream>
#include<string.h>

using namespace std;
const int N=6;
int v[N]={0,12,3,10,3,6};//價值  
int w[N]={0,5,4,7,2,6}; //重量

int dp(int n,int m)//n物品個數,m揹包容量 
{
	int Max;
	if(n==0)
	    return 0;
	Max=dp(n-1,m);
	for(int i=1;i<=m/w[n];i++)
	{
		Max=max(Max,dp(n-1,m-w[n]*i)+v[n]*i);//三個比較求出最大值,注意下標比較多,區分開來
	}
	return Max;	
}

int main() 
{   
	int n=dp(5,15);
	cout<<n;	   
}   

  

3、

P03: 多重揹包問題

題目

有N種物品和一個容量爲V的揹包。第i種物品最多有n[i]件可用,每件費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可以使這些物品的費用總和不超過揹包容量,且價值總和最大。

 

一、將多重揹包轉化爲徹底揹包,只是將不肯定的個數取n和c/w[i]的最小值,其餘不變;

#include<iostream>
#include<cmath>

using namespace std;
int Num[100][100]={0};
int main()
{
    int n=3,c=8;
	int w[4]={0,1,2,2};
	int v[4]={0,6,10,20};
	int N[4]={0,10,5,2};
	
	for(int i=1;i<=n;i++)  //肯定矩陣 
	{
		for(int j=1;j<=c;j++)
		{
			int d=min(j/w[i],N[i]);
			for(int k=1;k<=d;k++)
			{
				Num[i][j]=max(Num[i-1][j],Num[i-1][j-k*w[i]]+k*v[i]);
			}
		}
	}
	for(int j=1;j<=n;j++)
	{
		for(int i=1;i<=c;i++)
	    {
		    cout<<Num[j][i]<<" ";
	    }
	    cout<<endl;
	}
	
	int t[4]={0};//肯定每種物品個數 
	int s=c;//價值總數 
	for(int i=n;i>0;i--)
	{
		int d=min(c/w[i],N[i]);
	    for(int k=d;k>0;k--)
		{
		    if(Num[i][s]==(Num[i-1][s-k*w[i]]+k*v[i]))//找出最大的加的數是什麼,進而求出個數 
		    {
		    	t[i]=k;
		    	s=s-k*w[i];
		    	break;
			}
		}		
	} 
	for(int i=1;i<=n;i++)
	    cout<<t[i]<<" ";
	cout<<endl;	
	
} 
//3 8 1 6 10 2 10 5 2 20 2
/*3 8           //3件物品,揹包承重最大爲8
1 6 10        //第一件物品, 重量爲1,價值爲6, 數目爲10
2 10 5
2 20 2*/

求矩陣是轉化爲了徹底揹包,只是改了個數。

求每種物品具體用的個數本想着計算的過程當中就得出,可是因爲只有到運算最後才能得出最大的值,因此過程當中並很差判斷,只能先求出矩陣後再計算。

計算過程以下:64=24+2*20;因此三好物品用了兩個;24=4*6;因此一號物品用了4個,求出。  

二、改進空間複雜度

沒找到和想出簡單易於理解的程序。

三、

#include <iostream>
using namespace std;


int knapsack_limitnum(int *W, int *V, int *N, int *res, int n, int C)
{
    int value = 0;
    int **f = new int*[n];
    for(int i = 0; i < n; i++)
    {
        f[i] = new int[C+1];
    }
    for(int i = 0; i < n; i++)
        for(int j = 0; j < C+1; j++)
            f[i][j] = 0;

    for(int y = 1; y < C+1; y++)
    {
        int count = min(N[0], y/W[0]);
        f[0][y] = (y < W[0])?0:(count * V[0]);
    }

    for(int i = 1; i < n; i++)
    {
        for(int y = 1; y < C+1; y++)
        {
            if(y < W[i])
            {
                f[i][y] = f[i-1][y];
            } else {
                int count = min(N[i], y/W[i]);
                f[i][y] = f[i-1][y];
                for(int k = 1; k <= count; k++)
                {
                    int temp = f[i-1][y-W[i]*k] + k*V[i];
                    if(temp >= f[i][y])
                        f[i][y] = temp;
                }
            }
        }
    }

    for(int i = 0; i < n; i++)
    {
        for(int y = 0; y < C+1; y++)
            cout << f[i][y] << " ";
        cout << endl;
    }

    value = f[n-1][C];
    int j = n-1;
    int y = C;
    while(j)
    {
        int count = min(N[j], y/W[j]);
        for(int k = count; k > 0; k--)
        {
            if(f[j][y] == (f[j-1][y-W[j]*k]+k*V[j]))
            {
                res[j] = k;
                y = y - k*W[j];
                break;
            }
        }
        j--;
    }
    res[0] = f[0][y]/V[0];


    for(int i = 0;i < n; i++)
    {
        delete f[i];
        f[i] = 0;
    }
    delete [] f;
    f = 0;
    return value;
}

void test1()
{
    int n, C;
    while(cin >> n >> C)  //n:物品個數,C:承重量 
    {
        int *W = new int[n];
        int *V = new int[n];
        int *N = new int[n];
        int *res = new int[n];
        for(int i =0; i < n; i++)
            res[i] = 0;    //每種物品存放的個數 
        int w, v, n1, i = 0;
        while(i < n)  //循環輸入 
        {
            cin >> w >> v >> n1;
            W[i] = w;  //重量 
            V[i] = v;    //價值 
            N[i] = n1;   //數目 
            i++;
        }
        int value = knapsack_limitnum(W, V, N, res, n, C);
        cout << value << endl;//最大值 
        for(int i = 0; i < n; i++)
            cout << res[i] << " ";//個數 
        cout << endl;
        delete res; //res = 0; //釋放空間 
        delete N; //N = 0;
        delete V; //V = 0;
        delete W; //W = 0;
    }
}


int main()
{
    test1();
    return 0;
}
//3 8 1 6 10 2 10 5 2 20 2
/*3 8           //3件物品,揹包承重最大爲8
1 6 10        //第一件物品, 重量爲1,價值爲6, 數目爲10
2 10 5
2 20 2*/

 四、

本身編寫循環輸入和函數調用輸出的整個程序。

(1)

#include<iostream>
#include<cmath>

using namespace std;
int Num[100][100]={0};
int w[100]={0},v[100]={0},N[100]={0};
 
int main()
{
    //int n=3,c=8;
	//int w[4]={0,1,2,2};
	//int v[4]={0,6,10,20};
	//int N[4]={0,10,5,2};
	int n,c;
	while(cin>>n>>c)
	{
		for(int i=1;i<=n;i++)
		{
			cin>>w[i]>>v[i]>>N[i];
		}
		
		for(int i=1;i<=n;i++)  //肯定矩陣 
		{
			for(int j=1;j<=c;j++)
			{
				int d=min(j/w[i],N[i]);
				for(int k=1;k<=d;k++)
				{
					Num[i][j]=max(Num[i-1][j],Num[i-1][j-k*w[i]]+k*v[i]);
				}
			}
		}
		for(int j=1;j<=n;j++)
		{
			for(int i=1;i<=c;i++)
		    {
			    cout<<Num[j][i]<<" ";
		    }
		    cout<<endl;
		}
		
		int t[4]={0};//肯定每種物品個數 
		int s=c;//價值總數 
		for(int i=n;i>0;i--)
		{
			int d=min(c/w[i],N[i]);
		    for(int k=d;k>0;k--)
			{
			    if(Num[i][s]==(Num[i-1][s-k*w[i]]+k*v[i]))//找出最大的加的數是什麼,進而求出個數 
			    {
			    	t[i]=k;
			    	s=s-k*w[i];
			    	break;
				}
			}		
		} 
		for(int i=1;i<=n;i++)
		    cout<<t[i]<<" ";
		cout<<endl;	
		
		Num[100][100]={0};
        w[100]={0};v[100]={0};N[100]={0};
    }
    return 0;
} 
//3 8 1 6 10 2 10 5 2 20 2
/*3 8           //3件物品,揹包承重最大爲8
1 6 10        //第一件物品, 重量爲1,價值爲6, 數目爲10
2 10 5
2 20 2*/

這是採用聲明全局變量方法作的,也沒有調用函數

(2)

#include<iostream>
#include<cmath>

using namespace std;
int Num[100][100]={0};
int w[100]={0},v[100]={0},N[100]={0};

void dp(int *w,int *v,int *N,int n,int c)//注意矩陣地址的引用
{
	for(int i=1;i<=n;i++)  //肯定矩陣 
		{
			for(int j=1;j<=c;j++)
			{
				int d=min(j/w[i],N[i]);
				for(int k=1;k<=d;k++)
				{
					Num[i][j]=max(Num[i-1][j],Num[i-1][j-k*w[i]]+k*v[i]);
				}
			}
		}
		for(int j=1;j<=n;j++)
		{
			for(int i=1;i<=c;i++)
		    {
			    cout<<Num[j][i]<<" ";
		    }
		    cout<<endl;
		}
		
		int t[4]={0};//肯定每種物品個數 
		int s=c;//價值總數 
		for(int i=n;i>0;i--)
		{
			int d=min(c/w[i],N[i]);
		    for(int k=d;k>0;k--)
			{
			    if(Num[i][s]==(Num[i-1][s-k*w[i]]+k*v[i]))//找出最大的加的數是什麼,進而求出個數 
			    {
			    	t[i]=k;
			    	s=s-k*w[i];
			    	break;
				}
			}		
		} 
		for(int i=1;i<=n;i++)
		    cout<<t[i]<<" ";
		cout<<endl;	
}
 
int main()
{
    //int n=3,c=8;
	//int w[4]={0,1,2,2};
	//int v[4]={0,6,10,20};
	//int N[4]={0,10,5,2};
	int n,c;
	while(cin>>n>>c)
	{
		for(int i=1;i<=n;i++)
		{
			cin>>w[i]>>v[i]>>N[i];
		}
		
		dp(w,v,N,n,c);
		
		Num[100][100]={0};
        w[100]={0};v[100]={0};N[100]={0};
    }
    return 0;
} 
//3 8 1 6 10 2 10 5 2 20 2
/*3 8           //3件物品,揹包承重最大爲8
1 6 10        //第一件物品, 重量爲1,價值爲6, 數目爲10
2 10 5
2 20 2*/

這是採用函數形式,注意定義函數調用矩陣時,用地址*。

 

 

4、

P04: 混合三種揹包問題

問題

若是將P01P02P03混合起來。也就是說,有的物品只能夠取一次(01揹包),有的物品能夠取無限次(徹底揹包),有的物品能夠取的次數有一個上限(多重揹包)。應該怎麼求解呢?

5、

P05: 二維費用的揹包問題

問題

二維費用的揹包問題是指:對於每件物品,具備兩種不一樣的費用;選擇這件物品必須同時付出這兩種代價;對於每種代價都有一個可付出的最大值(揹包容量)。問怎樣選擇物品能夠獲得最大的價值。設這兩種代價分別爲代價1和代價2,第i件物品所需的兩種代價分別爲a[i]和b[i]。兩種代價可付出的最大值(兩種揹包容量)分別爲V和U。物品的價值爲w[i]。

6、

P07: 有依賴的揹包問題

簡化的問題

這種揹包問題的物品間存在某種「依賴」的關係。也就是說,i依賴於j,表示若選物品i,則必須選物品j。爲了簡化起見,咱們先設沒有某個物品既依賴於別的物品,又被別的物品所依賴;另外,沒有某件物品同時依賴多件物品。

7、

P08: 泛化物品

定義

考慮這樣一種物品,它並無固定的費用和價值,而是它的價值隨着你分配給它的費用而變化。這就是泛化物品的概念。

更嚴格的定義之。在揹包容量爲V的揹包問題中,泛化物品是一個定義域爲0..V中的整數的函數h,當分配給它的費用爲v時,能獲得的價值就是h(v)。

8、

P09: 揹包問題問法的變化

以上涉及的各類揹包問題都是要求在揹包容量(費用)的限制下求能夠取到的最大價值,但揹包問題還有不少種靈活的問法,在這裏值得提一下。可是我認爲,只要深刻理解了求揹包問題最大價值的方法,即便問法變化了,也是不難想出算法的。

例如,求解最多能夠放多少件物品或者最多能夠裝滿多少揹包的空間。這均可以根據具體問題利用前面的方程求出全部狀態的值(f數組)以後獲得。

還有,若是要求的是「總價值最小」「總件數最小」,只需簡單的將上面的狀態轉移方程中的max改爲min便可。

下面說一些變化更大的問法。

相關文章
相關標籤/搜索