01背包问题

01背包问题

1.问题重述

题目
有n个重量和价值为wi,vi的物品,从这些物品中挑选出总重量不超过M的物品,求所有可选方案中价值总和的最大值。
1<=n<=100
1<=wi,vi<=100
1<=W<=10000
输入:
n=4
(w,v)={{2,3},{1,2},{3,4},{1,1}}
W=5
输出:
7(选择0,1,3号物品)

2.采用dfs深度优先搜索

//和子集、全排列很像 ,选还是不选 
//用dfs解决 
#include<bits\stdc++.h>
using namespace std;

//全局不需要传参 
int w[]={2,1,3,2};//重量表 
int v[]={3,2,4,2};//价格表	
int n=4;
int W=5;
int rec[5][6];



int dfs(int i,int ww)//从i号物品开始选 ,ww为还可以装物品重量 
{
	int v1,v2,ans;
	if(ww<0) return 0;//装不进去 
	if(i==n) return 0;
	
  	v2=dfs(i+1,ww);//不选当前物品 
	if(ww>=w[i]){
		v1=v[i]+dfs(i+1,ww-w[i]);//选择当前物品
		return max(v1,v2); 
	}else{
		return v2;
	}
}


int main()
{
	int ww=W;//保存变量W
	memset(rec,-1,sizeof(rec));//初始化全为-1
	int ans=dfs(0,ww);
	//int ans=dfs1(0,ww);
	cout<<ans<<endl;	
	return 0;
} 
//出现了重叠子问题
//解决方法:记忆形递归,带备忘录 
//rec[][]增加速度很多 

改进:增加记忆化搜索,加快搜索速度

int dfs1(int i,int ww)//从i号物品开始选 ,ww为还可以装物品重量 
{
	int v1,v2,ans;
	if(ww<0) return 0;//装不进去 
	if(i==n) return 0; 
	//1.计算之前先查询 
  	v2=dfs(i+1,ww);//不选当前物品 
	if(ww>=w[i]){
		v1=v[i]+dfs(i+1,ww-w[i]);//选择当前物品
		ans=max(v1,v2); 
	}else{
		ans=v2;
	}
	//2.计算之后做保存 
	rec[i][ww]=ans;
	return ans; 
}

3.采用动态规划dp求解

用excel找规律(dp问题要理解表格),算法根据填表逻辑写;
找出变化的量:容量变化、物品范围变化、价格变化;
两个自变量(包的容量,物品选哪些),一个因变量(价格)
画出二维表

(w,v)物品编号(当前编号以及之前的范围)012345
(2,3)0003333
(1,2)102=max(要:2+0,不要:0)3=max(要:2+0,不要:3)5=max(2+3,3)5=max(2+3,3)5=max(2+3,3)
(3,4)20235=(4+0,5)6=(4+2,5)7=(4+3,3)
(2,2)3023=(2+0,3)5=(2+3,5)6=(2+3,6)7=(2+5,7)

最右下角的单元格即为所求
从历史数据处理得出结果 :
我不要这个物品:找上方的单元格
要的话:本物品价格+减去当前物品容量的上一行单元格对应的价格

根据表格写出算法如下:

int dp()
 {
 	int dp[n][W+1];
 	//设置边界
 	for(int i=0;i<W+1;i++){
 		if(i>=w[0]){//每种容量和我们的0号物品比较,大的话就可以装进
		 dp[0][i]=v[0]; 
 		}else{
 			dp[0][i]=0;//初始化dp表的第一行 
 		}
 	} 
 	//其他行
	 for(int i=1;i<n;i++){
	 	//j是列,也是背包的剩余容量
		 for(int j=0;j<W+1;j++){
		 	if(j>=w[i]){//要的起 
		 		int v1=	v[i]+dp[i-1][j-w[i]];//选择当前物品即i号物品,剩余容量 
		 		int v2=dp[i-1][j];
		 		dp[i][j]=max(v1,v2);
			 }else{//要不起 
			 	dp[i][j]=dp[i-1][j];
			 }
		 } 
	 }
	 return dp[n-1][W]; 
 } 
 
 
 
 //递归要等子问题结果
 //dp不用等,直接用已有问题 
int main() 
{
	
	int ans=dp();
	cout<<ans<<endl;	
	return 0;
 }	

附上oj上答案(有些不同)

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;

int Max(int a,int b)
{
	return a>b?a:b;
} 
int f[1001][1001];

int main()
{
	int t,n,v;
	int N[1001],V[1001];
	cin>>t;
	while(t--)
	{
		cin>>n>>v;
		for(int i=1;i<=n;i++)
		{
			cin>>N[i];//价值 
		}
		for(int i=1;i<=n;i++)
		{
			cin>>V[i];//体积 
		}
		memset(f,0,sizeof(f));//把f数组清零
		//和上述算法不同,边界是从第一列考虑的
		for(int i=1;i<=n;i++){
			for(int j=0;j<=v;j++){
				if(j>=V[i])//可从f[i-1][j-V[i]]反推,若j-V[i]<0索引出错
					f[i][j]=Max(f[i-1][j],f[i-1][j-V[i]]+N[i]);
				else f[i][j]=f[i-1][j]; 
			}
		}
		cout<<f[n][v]<<endl;
    }
	return 0;
}

小建议:希望把用dp算法解决的问题的数组名都改为dp

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值