算法(二)——背包问题

一、

P01: 01背包问题

N件物品和一个容量为V 的背包。放入第i件物品耗费的费用是Ci1,得到的价值是Wi。求解将哪些物品装入背包可使价值总和最大。 

价值数组v = {8, 10, 6, 3, 7, 2},

重量数组w = {4, 6, 2, 2, 5, 1},

背包容量C = 12。不超过容量的情况下,使得价值最大。

1、

#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列,优化空间

2、

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

大大节省空间。

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

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

4、利用递归的方法求解

#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);
}   

利用这样的方法,程序简单,但是会增加时间复杂度。

5、将该问题改为循环输入

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

6、

通过指针来传递数组

#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); 
}

 

二、

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: 返回最大的总价值
"""*/

代码实现

1、

#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个物品状态比较,

2、空间的优化

#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件的不同,而不跨越到上一个物品,而和上一个物品比较则根据下标来比的,简化了好多。 

3、递归实现

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

  

三、

P03: 多重背包问题

题目

有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

 

1、将多重背包转化为完全背包,只是将不确定的个数取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个,求出。  

2、改进空间复杂度

没找到和想出简单易于理解的程序。

3、

#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*/

 4、

自己编写循环输入和函数调用输出的整个程序。

(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*/

这是采用函数形式,注意定义函数调用矩阵时,用地址*。

 

 

四、

P04: 混合三种背包问题

问题

如果将P01P02P03混合起来。也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢?

五、

P05: 二维费用的背包问题

问题

二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为a[i]和b[i]。两种代价可付出的最大值(两种背包容量)分别为V和U。物品的价值为w[i]。

六、

P07: 有依赖的背包问题

简化的问题

这种背包问题的物品间存在某种“依赖”的关系。也就是说,i依赖于j,表示若选物品i,则必须选物品j。为了简化起见,我们先设没有某个物品既依赖于别的物品,又被别的物品所依赖;另外,没有某件物品同时依赖多件物品。

七、

P08: 泛化物品

定义

考虑这样一种物品,它并没有固定的费用和价值,而是它的价值随着你分配给它的费用而变化。这就是泛化物品的概念。

更严格的定义之。在背包容量为V的背包问题中,泛化物品是一个定义域为0..V中的整数的函数h,当分配给它的费用为v时,能得到的价值就是h(v)。

八、

P09: 背包问题问法的变化

以上涉及的各种背包问题都是要求在背包容量(费用)的限制下求可以取到的最大价值,但背包问题还有很多种灵活的问法,在这里值得提一下。但是我认为,只要深入理解了求背包问题最大价值的方法,即使问法变化了,也是不难想出算法的。

例如,求解最多可以放多少件物品或者最多可以装满多少背包的空间。这都可以根据具体问题利用前面的方程求出所有状态的值(f数组)之后得到。

还有,如果要求的是“总价值最小”“总件数最小”,只需简单的将上面的状态转移方程中的max改成min即可。

下面说一些变化更大的问法。

posted on 2018-04-20 10:15  箬笠蓑衣  阅读(487)  评论(0编辑  收藏  举报