您的位置:首页 > 其它

背包问题(dp动态规划思路详解)

2017-11-29 17:31 429 查看
背包问题1---动态DP
题目:有n个重量和价值分别为wi,vi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大的值。

输入:n = 4, (w, v)  = {(2, 3), (1, 2), (3, 4), (2, 2)},W = 5

输出:W = 5

思路:
首先我们采用递归的想法。定义一个函数 int rec(int i, int j)。这个函数的作用是求得挑选的最佳效果。调用的时候分别传入0,W。
然后我们就要开始递归了,采用分治策略。
我们从0个物品开始向后思考是否要选取这个物品
int rec(int i, int j){
int res;
if(i == n){如果这个物品已经超出范围了,因为i传入的时候是0,最后一个i应该是n-1 
说明我们无法选择了:
res = 0;
}esle if(j < w[i]){
如果当前这个物品的重量比我们还需要选的重量还要大,就选不了这个物品
选不了我们就跳过它选下一个:
res = rec(i + 1, j);
//这里喃我们需要一种抽象思想,就是通过rec函数就能	找出最佳的res。这个物品选不了了,我们从下一个	物品开始选出最佳答案。
}else {
可以选了,我们比较选它和不选它那个是更有价值的:
res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);
// rec(i + 1, j):同上,表示我们不选它,从下一个开始选出最佳解
//rec(i + 1, j - w[i]) + v[i]:我们知道调用rec(i + 1, j - w[i])选了本物品后剩下还要选的重量在下一个开始的	最	大价值 + 本物品价值,也就是总的rec(i, j)的最大价值
}
}

递归代码:

/*
输入:
4
2 3
1 2
3 4
2 2
5
输出:7
*/
#include<cstdio>
#include<algorithm>
using namespace std;
int n, W;
const int MAX_N = 100;
int w[MAX_N], v[MAX_N];
//从第i个物品开始挑选总重量小于j的部分
int rec(int i, int j){
int res;
if(i == n){
//已经没有剩余的物品了
res = 0;
}else if(j < w[i]){
//无法挑选这个物品(判断下一个)
res = rec(i + 1, j);
}else{
//不挑选和挑选的两种情况分别尝试一下
res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);
}
return res;//返回挑选了价值
}
void solve(){
printf("%d\n", rec(0, W));
}
int main(){

while(scanf("%d", &n) != EOF){
for(int i = 0; i < n; i++){
scanf("%d%d", &w[i], &v[i]);
}
scanf("%d", &W);
solve();
}
}
但是我们发现些时候递归是很重复的,所以我们定义一个数组dp来保存每次递归的结果,如果已经递归过,我们就直接返回结果

int dp[MAX_N + 1][MAX_W + 1];//记忆化数组

int rec(int i, int j){
if(dp[i][j] >= 0){
//已经计算过的话直接使用之前的结果
return dp[i][j];
}
int res;
if(i == n){
res = 0;
}else if(j < w[i]){
res = rec(i + 1, j);
}else{
res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);
}
//将结果记录在数组中
return dp[i][j] = res;
}
void solve(){
//用-1表示尚未计算过,初始化整个数组
//使用memset可以对1字节为单位进行填充。对1,0可进行高速初始化。不过无法初始化化为1
memset(dp, -1, sizeof(dp));
printf("%d\n", rec(0, W));
}

后来我们又发现明明数组就能搞定的为啥还要递归喃?所以直接用dp数组就可以解决问题了。

下面我们进行逆向循环规划:

int dp[MAX_N + 1][MAX_W + 1];//要都初始化成0

void solve(){
for(int i = n - 1; i >= 0; i--){//我们从最后一个物品开始选(逆向)
for(int j = 0; j <= W; j++){
if(j < w[i]){//j表示可以选到的重量,w[i]是当前物品的重量
dp[i][j] = dp[i + 1][j];
}else{
dp[i][j] = max(dp[i + 1][j], dp[i + 1][j - w[i]] + v[i]);
}
}
}
printf("%d\n", dp[0][W]);
}
下面我们进行正向循环规划:
void solve(){
for(int i = 0; i < n; i++){
for(int j = 0; j <= W; j++){
if(j < w[i]){
dp[i + 1][j] = dp[i][j];
}else{
dp[i + 1][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]);
}
}
}
printf("%d\n", dp
[W]);
}
其他的dp方式:
void solve(){
for(int i = 0; i < n; i ++){
for(int j = 0; j <=W; j++){
dp[i + 1][j] = max(dp[i + 1][j], dp[i][j]);
if(j + w[i] <= W){
dp[i + 1][j + w[i]] = max(dp[i + 1][j + w[i]], dp[i][j] + v[i]);
}
}
}
printf("%d\n", dp
[W]);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: