0-1 背包问题详解

给定一组有固定价值和固定重量的物品,以及一个已知最大承重量的背包,求在不超过背包最大承重量的前提下,能放进背包里面的物品的最大总价值。

这一类问题是典型的使用动态规划解决的问题,我们可以把背包问题分成3种不同的子问题:0-1背包问题、完全背包和多重背包问题。下面对这三种问题分别进行讨论。

1.0-1背包问题

0-1背包问题是指每一种物品都只有一件,可以选择放或者不放。现在假设有n件物品,背包承重为m。

对于这种问题,我们可以采用一个二维数组去解决:f[i][j],其中i代表加入背包的是前i件物品,j表示背包的承重,f[i][j]表示当前状态下能放进背包里面的物品的最大总价值。那么,f[n][m]就是我们的最终结果了。

采用动态规划,必须要知道初始状态和状态转移方程。初始状态很容易就能知道,那么状态转移方程如何求呢?对于一件物品,我们有放进或者不放进背包两种选择:

(1)假如我们放进背包,f[i][j] = f[i - 1][j - weight[i]] + value[i],这里的f[i - 1][j - weight[i]] + value[i]应该这么理解:在没放这件物品之前的状态值加上要放进去这件物品的价值。而对于f[i - 1][j - weight[i]]这部分,i - 1很容易理解,关键是 j - weight[i]这里,我们要明白:要把这件物品放进背包,就得在背包里面预留这一部分空间。
(2)假如我们不放进背包,f[i][j] = f[i - 1][j],这个很容易理解。因此,我们的状态转移方程就是:f[i][j] = max(f[i][j] = f[i - 1][j] , f[i - 1][j - weight[i]] + value[i]) 。当然,还有一种特殊的情况,就是背包放不下当前这一件物品,这种情况下f[i][j] = f[i - 1][j]。


#include <iostream>
#define V 500
using namespace std;
int weight[20 + 1];
int value[20 + 1];
int f[V + 1];
int main() {
    int n, m;
    cout << "请输入物品个数:";
    cin >> n;
    cout << "请分别输入" << n << "个物品的重量和价值:" << endl; 
    for (int i = 1; i <= n; i++) {
        cin >> weight[i] >> value[i];
    }
    cout << "请输入背包容量:";
    cin >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = m; j >= 1; j--) {
            if (weight[i] <= j) {
                f[j] = f[j] > f[j - weight[i]] + value[i] ? f[j] : f[j - weight[i]] + value[i];
            }
        }
    }
    cout << "背包能放的最大价值为:" << f[m] << endl;
}

题目描述

这个表格对于理解0-1背包问题很有用,我们利用它来理解一下为什么要把第二层循环颠倒这个问题。考虑d9这一项,要求出这个状态,我们有可能利用到的就是e1到e8这8个状态,当我们把第二层循环颠倒过来时,当我们要求f[j]时,f[j -1]到f[1]还保存着下面一行的状态,因此可以采用一维数组解决。我们可以考虑一下假如不把第二层循环颠倒,当要求f[j]时,f[j - 1]到f[1]已经是同一行的状态了,根本没法求。

for (int i = 1; i <= n; i++) {
     for (int j = m; j >= 1; j--) {
         if (weight[i] <= j) {
             f[j] = f[j] > f[j - weight[i]] + value[i] ? f[j] : f[j - weight[i]] + value[i];
         }
     }
}

2.完全背包问题

#include <iostream>
#define V 500
using namespace std;
int weight[20 + 1];
int value[20 + 1];
int f[V + 1];
int max(int a, int b) {
    return a > b ? a : b;
}
int main() {
    int n, m;
    cout << "请输入物品个数:";
    cin >> n;
    cout << "请分别输入" << n << "个物品的重量和价值:" << endl; 
    for (int i = 1; i <= n; i++) {
        cin >> weight[i] >> value[i];
    }
    cout << "请输入背包容量:";
    cin >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = weight[i]; j <= m; j++) {
            f[j] = max(f[j], f[j - weight[i]] + value[i]);
        }
    }
    cout << "背包能放的最大价值为:" << f[m] << endl;
}

完全背包问题与0-1背包问题的区别
1.完全背包问题内层循环是顺序的,而0-1背包问题是逆序的。。
0-1背包为逆序的原因是为了求max中的两项是前一状态的值。
而现在是要求当前状态的值,因为每种背包都是无限的,当我们从1到N循环时,f[v]表示容量为v在前i种背包所得的价值,这里我们添加的不是前一个背包,而且当前背包,所以应考虑当前状态。

3.多重背包问题

多重背包问题限定了一种物品的个数,解决多重背包问题,只需要把它转化为0-1背包问题即可。比如,有2件价值为5,重量为2的同一物品,我们就可以分为物品a和物品b,a和b的价值都为5,重量都为2,但我们把它们视作不同的物品。

#include <iostream>
using namespace std;
#define V 1000
int weight[50 + 1];
int value[50 + 1];
int num[20 + 1];
int f[V + 1];
int max(int a, int b) {
    return a > b ? a : b;
}
int main() {
    int n, m;
    cout << "请输入物品个数:";
    cin >> n;
    cout << "请分别输入" << n << "个物品的重量、价值和数量:" << endl; 
    for (int i = 1; i <= n; i++) {
        cin >> weight[i] >> value[i] >> num[i];
    }
    int k = n + 1;
    for (int i = 1; i <= n; i++) {
        while (num[i] != 1) {
            weight[k] = weight[i];
            value[k] = value[i];
            k++;
            num[i]--;
        }
    }
    cout << "请输入背包容量:";
    cin >> m;
    for (int i = 1; i <= k; i++) {
        for (int j = m; j >= 1; j--) {
            if (weight[i] <= j) f[j] = max(f[j], f[j - weight[i]] + value[i]);
        }
    }
    cout << "背包能放的最大价值为:" << f[m] << endl;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • 网上好多关于背包问题的解释,自己也看了,感觉解释的不容易通俗易懂,所以自己来写一个非常容易懂得。 0-1背包问题说...
    sydt2011阅读 36,847评论 3 17
  • 我在进行一些互联网公司的技术笔试的时候,对于我来说最大的难题莫过于最后的那几道编程题了,这对算法和数据结构有一定程...
    柠檬乌冬面阅读 19,849评论 2 19
  • 感冒继续,去药店买药,需要登记身份证,说了号码也就卖了。都说感冒吃不吃药都要七天,不过吃药可以缓解一下症状,身体不...
    董克平日记阅读 497评论 0 0
  • 一圈圈, 一层层, 我在外面,你在里面。 你说, 天气很好, 我说, 这里暴雨连连, 你怀疑我, 我怀疑你。
    小娘纸阅读 307评论 0 3
  • 一、版本控制 用文档记录每个模块的改动,存储、追踪修改历史。方便恢复版本以及版本查看。 二、版本控制软件 - SV...
    学飞的小鸡阅读 239评论 0 0