算法设计期末考试(七天冲刺)最终篇

目录

背包问题

一、0-1背包问题(物品不能分割dp)

1.0-1背包(倒着写)

2.0-1背包

3.0-1背包一维

二、部分背包问题(物品可以分割 贪心算法)

三、完全背包一维滚动数组


背包问题

一、0-1背包问题(物品不能分割dp)

1.0-1背包(倒着写)

物品编号1234
质量W2132
价值v12102015

 背包容量c=5,物品个数n=4,物品价值v,p[i][j]:[0,i]的物品任取放入容量为j的背包。

不放物品i,背包容量为j,它能装的最大价值为:p[i-1][j];

放物品i,背包容量为j,它能装的最大价值为:背包容量-物品i的容量 所能放的最大价值+物品i的价值。p[i-1][j-w[i]]+v[i]

p[i][j]=max(p[i-1][j-w[i]]+v[i],p[i-1][j])

p[i][j]=\left\{\begin{matrix} p[i-1][j] &&0<=j<=wi \\ p[i-1][j-w[i]]+v[i] && j>wi\end{matrix}\right.  

当前元素的值是由上方(p[i-1][j])和左上角元素(p[i-1][j-w[i]]+v[i])的值推出来的。

 图示计算过程,在装物品4时,去除物品4的质量背包所剩容量为3。

背包容量为1时,最大价值为10,1号物品;

背包容量为2时,最大价值为12,2号物品;

背包容量为3时,最大价值为22,1,2号物品;

所以背包能装的最大价值为:(背包容量5-物品i的容量2) 所能放的最大价值+物品i的价值。22+15=37。

背包容量j
p[i][j]012345
0000000
物品编号i10012↖12←12←12←
2010↖12↑22↖22←22←
3010↑12↑22↑30↖32↖
4010↑15↖25↖30↑37↖

 

#include<bits/stdc++.h>
#define NUM 10
#define CAP 6
using namespace std;
int V[NUM];
int W[NUM];
int P[NUM][CAP];
int main()
{
	int c;//背包容量
	int n;//物品个数
	scanf("%d",&c);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&W[i],&V[i]);
	for(int i=1;i<=n;i++)
	{
	
		for(int j=c;j>=1;j--)
		{
			if(W[i]<=j) P[i][j]=max(P[i-1][j],P[i-1][j-W[i]]+V[i]);
			else P[i][j]=P[i-1][j];
			printf(" %d ",P[i][j]);
		}
		printf("\n");
	
	}	
	printf("%d",P[n][c]);
	return 0;
}
输入:
5 4
2 12
1 10
3 20
2 15
输出:
 12  12  12  12  0
 22  22  22  12  10
 32  30  22  12  10
 37  30  25  15  10
37

2.0-1背包

#include<bits/stdc++.h>
#define NUM 10
#define CAP 6
using namespace std;
int V[NUM];
int W[NUM];
int P[NUM][CAP];


int main()
{
	int c;//背包容量
	int n;//物品个数
	scanf("%d",&c);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&W[i],&V[i]);
	
	for(int i=1;i<=n;i++)
	{
	
		for(int j=1;j<=c;j++)
		{
			if(W[i]<=j) P[i][j]=max(P[i-1][j],P[i-1][j-W[i]]+V[i]);
			else P[i][j]=P[i-1][j];
			printf(" %d ",P[i][j]);
		}
		printf("\n");
	
	}	
	printf("%d",P[n][c]);
	return 0;
}
输入:
5 4
2 12
1 10
3 20
2 15
输出:
 0  12  12  12  12
 10  12  22  22  22
 10  12  22  30  32
 10  15  25  30  37
37

3.0-1背包一维

p[i][j]=max(p[i-1][j-w[i]]+v[i],p[i-1][j]),当前层的数值是由上一层推导出来的,可以把i-1的数据拷贝到第i层,直接在第i层计算,新的值覆盖当前层的数据,每次计算都在更新这一层的数据(滚动数组),实现二维数组的降维操作。

p[j]:容量为j的背包所能装的最大价值

二维递推公式:p[i][j]=max(p[i-1][j-w[i]]+v[i],p[i-1][j])

一维递推公式:

不放物品i p[j](p[i-1][j]已经拷贝到当前层)

放物品i p[i-w[i]]+v[i]

p[j]=max(p[j],p[i-w[i]]+v[i])

p[j]=\left\{\begin{matrix} p[j] &&0<=j<=wi \\ p[j-w[i]]+v[i] && j>=wi \end{matrix}\right. 

 一维数组只能倒叙遍历,元素只被添加一次。

物品编号1234
质量W2132
价值v12102015

背包容量:

543210

物品: 

1212121200
22222212100
32302212100
37302515100

 遍历到物品4时,p[5-2]+v=p[3]+15

求背包容量为3时背包能装的最大价值,p[3]的最大值是,装了物品1和物品2时背包的价值。

#include<bits/stdc++.h>
#define NUM 10

using namespace std;
int V[NUM];
int W[NUM];
int P[NUM];


int main()
{
	int c;//背包容量
	int n;//物品个数
	scanf("%d",&c);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&W[i],&V[i]);
	
	for(int i=1;i<=n;i++)
	{
	
		for(int j=c;j>=1;j--)
		{
			if(W[i]<=j) P[j]=max(P[j],P[j-W[i]]+V[i]);
			printf(" %d ",P[j]);
		}
		printf("\n");
	
	}	
	printf("%d",P[c]);
	return 0;
}
输入:
5 4
2 12
1 10
3 20
2 15
输出:
 12  12  12  12  0
 22  22  22  12  10
 32  30  22  12  10
 37  30  25  15  10
37

二、部分背包问题(物品可以分割 贪心算法)

物品可以是可以分割的金币,钻石,分割之后物品的单位价值不变。往里面装尽可能值钱的物品,所以优先选择单位价值高的物品。背包容量50,先装性价比最高的1号物品,再装质量20的2号物品,最后剩20个容量全装3号物品,这种方式背包装的物品价值最高。

n123
质量102030
价值60100120
性价比654

#include<bits/stdc++.h>
using namespace std;
struct bag{
	int w;//质量
	int v;//价值
	double c;//性价比
}a[1001];
bool cmp(bag a,bag b)
{
	return a.c >= b.c;//从大到小
}
double knapsack(int n,bag a[],double c)
{
	double cleft = c;//背包的剩余容量
	int i=0;//用来增加金币堆数,背包放完一堆金币,层数加一
	double b = 0;//用来统计总价值
	while(i<n&&a[i].w<cleft)
	{
		cleft -= a[i].w;//装进物品,容量需要减去这个物品的质量
		b += a[i].v;//背包总价值增加
		i++; //已经装了一堆金币,层数加一
	}
	if(i<n) b+=1.0*a[i].v*cleft/a[i].w;
//直到装不下一堆完整的金币,背包价值=现在的背包价值+剩下金币的性价比*剩余容量

	return b;
}
int main()
{
	int c;//背包容量
	int n;//n堆金币
	int i;
	//while(scnaf("%d%d",&c,&n)&&c)
	scanf("%d%d",&c,&n);
		for(int i=0;i<=n-1;i++)
		{
			scanf("%d%d",&a[i].w,&a[i].v);//质量、价值
			a[i].c = 1.0*a[i].v/a[i].w;//性价比
		}
		sort(a,a+n,cmp);//对金币的单位价值从大到小排序
		printf("%15.3lf\n\n",knapsack(n,a,c));
	return 0;
	
}

三、完全背包一维滚动数组

每个物品可以使用无数次,可以一直装同一件物品。

    for(int i=1;i<=n;i++)
    { for(int j=1;j<=c;j++)//改成正序遍历物品就可以使用无数次
        {
            if(W[i]<=j) P[j]=max(P[j],P[j-W[i]]+V[i]);
            printf(" %d ",P[j]);
        }
    }    

物品编号1234
质量W2132
价值v12102015

背包容量:

012345

物品: 

0012122424
01020304050
01020304050
01020304050

 当遍历到物品2时,p[1]=10,p[2]=p[2-1]+10=20,

p[5]=p[4]+10=p[3]+10+10=p[2]+10+10+10=50。以此实现了多次装同一种物品。

#include<bits/stdc++.h>
#define NUM 10

using namespace std;
int V[NUM];
int W[NUM];
int P[NUM];
int main()
{
	int c;//背包容量
	int n;//物品个数
	scanf("%d",&c);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&W[i],&V[i]);
	
	for(int i=1;i<=n;i++)
	{
	
		for(int j=1;j<=c;j++)
		{
			if(W[i]<=j) P[j]=max(P[j],P[j-W[i]]+V[i]);
			printf(" %d ",P[j]);
		}
		printf("\n");
	
	}	
	printf("%d",P[c]);
	return 0;
}
输入:
5 4
2 12
1 10
3 20
2 15
输出:
 0  12  12  24  24
 10  20  30  40  50
 10  20  30  40  50
 10  20  30  40  50
50

本博客资料、代码来源于清华大学出版社算法设计与分析,本博客仅用于个人学习,可能存在纰漏,敬请批评指正。 

  • 29
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值