背包问题(01背包和无限背包)

假设有一个背包,体积是 V,另外有 n 个物品,物品的体积分别是 v1, v2, ... vn,每个物品的价值是 w1, w1, ... wn。求怎么将物品放到背包里,才能使背包中物品的价值最大 ?

背包问题是一个典型的动态规划问题。动态规划问题中经常包含一个最字,比如最大价值 ? 最短路径 ? 动态规划问题的求解思路包括以下几点:

(1)只看眼前利益

动态规划,关键字是动态,也就是说结果是在变化的。在计算过程中,只看眼前利益,只要当前这种情况满足要求,那么这就是中间的一个结果。

所有情况都遍历完之后的眼前利益就是最终想要的结果。

下边的代码是找数组的最大值。FindMax() 函数中,找最大值的时候,max 一直是已经遍历的数据的最大值,一直在更新,体现了只看眼前利益;max 也一直在更新,直到把数据都遍历完,max 就是最终的结果。这就是动态规划。

cpp 复制代码
#include <iostream>

int FindMax(int *data, int size) {
    int max = -1;
    for (int i = 0; i < 4; i++) {
        if (data[i] > max) {
            max = data[i];
        }
    }
    return max;
}

int main()
{
    int data[4] = {100, 200, 50, 10};
    std::cout << "max: " << FindMax(data, 4) << std::endl;
    return 0;
}

(2)选与不选

选与不选,就是分类讨论的思想。比如背包问题,当考虑一个物品时,要考虑两种情况,即这个物品放入背包的话,最终能放入的最大价值是多少;这个物品不放入背包的话,最终能放入的价值最大是多少。如果有 n 个物品,每个物品都要做这样的分类讨论,共有 2 的 n 次方种组合。把这些所有的情况的价值都计算出来,哪个组合的价值最大,那么这个组合就是最终的结果。

(3)记录历史信息

在动态规划的计算过程中,对一些情况的讨论往往会重复,在计算过程中可以记录历史信息,那么可以减小后边的重复计算。

背包问题分为两类:0-1 背包和无限背包。0-1 背包,说的是每个物品的数量只有一个, 也就是这个物品要么放进去,要么不放进去,只有两种情况。无限背包说的是每个物品的数量有无数个,每个物品都可以放 0 个,1 个或者多个。

数据遍历是很多算法的基础。

无论是排序算法,还是搜索算法或者动态规划。

算法的基础就是对一定数量的数据进行遍历,在遍历的过程中嵌入自己的算法逻辑,逻辑的不同就产生了了不同的算法。

数据保存在数据结构中,比如数组,链表,二叉树,图。每种数据结构都有自己的遍历方法。

1 0-1 背包

牛客网 01背包链接。

01背包

使用一维数组,时间复杂度是 O(n);使用选与不选的原始算法,时间复杂度是 O(2 的 n 次方)。所以优先选用以为数组。

1.1 一维数组

cpp 复制代码
#include<iostream>
#include<vector>
using namespace std;

// 能够放下的最大价值
int MaxValue(std::vector<int> &v, std::vector<int> &w, int n, int V) {
    // 数组的长度是 V + 1
    // 之所以比背包的体积大 1,这样就最大可以使用 V 做数组的下标了,便于使用
    std::vector<int> value(V + 1);
    // 数组元素的值是这个体积下能放下的价值,初始化为 0
    value.assign(V + 1, 0);
    // 两层循环,第一层循环遍历物品
    for (int i = 0; i < n; i++) {
        // 第二层循环遍历背包剩余的空间,能放下当前这个物品的空间
        // 体积从大到小进行遍历
        for (int j = V; j >= v[i]; j--) {
            // value[j] 是没放这个物品的时候,背包在 j 这个体积下的价值
            // value[j - v[i]] + w[i] 是放下这个物品的时候,j 体积下的价值
            if (value[j - v[i]] + w[i] > value[j]) {
                value[j] = value[j - v[i]] + w[i];
            }
        }
    }
    return value[V];
}

// 背包正好装满时的最大价值
int FullMaxValue(std::vector<int> &v, std::vector<int> &w, int n, int V) {
    std::vector<int> value(V + 1);
    // 与背包能放下的最大值比较的话
    // 初始值是不一样的
    // 将 value[0] 初始化为 0, 其它的元素初始化为一个非常小的数
    // 这个非常小的数要保证物品价值都加起来和这个数相加,也不会大于 0
    // 这样能保证正好装满的时候,value[V] 是大于 0 的
    // 最后可以通过 value[V] 是不是大于 0 来判断背包是不是可以正好装满
    // 如果能正好装满,那么 value[V] 价值是在 value[0] 也就是 0 的基础上加上物品的价值
    // 所以,value[V] 是大于 0 的。
    // 如果不能正好装满,那么 value[V] 的价值,在计算过程中,肯定与一个非常小的数进行了相加
    // 所以 value[V] 是小于 0 的
    value.assign(V + 1, -99999999);
    value[0] = 0;
    
    for (int i = 0; i < n; i++) {
        int tmp_v = v[i];
        int tmp_w = w[i];
        for (int j = V; j >= tmp_v; j--) {
            int value_old = value[j];
            int value_new = value[j - tmp_v] + tmp_w;
            if (value_new > value_old) {
                value[j] = value_new;
            }
        }
    }
    if (value[V] < 0) {
        return 0;
    }
    return value[V];
}

int main()
{
    int n = 0;
    int V = 0;
    std::vector<int> v;
    std::vector<int> w;
    cin >> n >> V;
    for (int i = 0; i < n; i++) {
        int a = 0;
        int b = 0;
        cin >> a >> b;
        v.push_back(a);
        w.push_back(b);
    }

    std::cout << MaxValue(v, w, n, V) << std::endl;
    std::cout << FullMaxValue(v, w, n, V);
    return 0;
}

1.2 选与不选

选与不选使用递归算法。递归算法的时间复杂度是O(2的 n 次方),时间复杂度太大,在牛客网上运行经常超时。使用一维数组的方式,时间复杂度是 O(n),所以有限选择数组的方式。

cpp 复制代码
#include<iostream>
#include<vector>
using namespace std;

// 保存背包能放得下的最大价值
int max_value = 0;
// 保存背包正好放满时的最大价值
int max_full_value = 0;
// 已放入的物品的价值
int value = 0;
// 已放入的物品的体积
int volume = 0;
void MaxValue(std::vector<int>& v, std::vector<int>& w, int n, int V,
              int index) {
    if (volume > V) {
      return;
    }
    if (index == n) {
        if (volume <= V && value > max_value) {
            max_value = value;
        }

        if (volume == V && value > max_full_value) {
            max_full_value = value;
        }
        return;
    }
    for (int i = index; i < n; i++) {
        // 选择这个物品
        volume += v[i];
        value += w[i];
        MaxValue(v, w, n, V, i + 1);
        // 不选择这个物品
        volume -= v[i];
        value -= w[i];
        MaxValue(v, w, n, V, i + 1);
    }
}

int main() {
    int n = 0;
    int V = 0;
    std::vector<int> v;
    std::vector<int> w;
    cin >> n >> V;
    for (int i = 0; i < n; i++) {
        int a = 0;
        int b = 0;
        cin >> a >> b;
        v.push_back(a);
        w.push_back(b);
    }

    MaxValue(v, w, n, V, 0);
    std::cout << max_value << std::endl;
    std::cout << max_full_value;
    return 0;
}

2 无限背包

无限背包也叫完全背包,牛客网链接如下。

完全背包

无限背包,说的是每个物品的个数都有无限个。可以使用一维数组的方式来求解,与 01 背包不同的是,在遍历体积的时候,需要从小到大进行遍历,01 背包是从大到小进行遍历的

为什么从小到大进行遍历呢,这样对于一个物品可以遍历到放置个的情况。比如一个背包的体积是 10,一个物品的体积是 2。如果从小到大进行遍历,那么只放这个物品的话,可以放置 5 个这样的物品,体积遍历到 2 的时候,可以放一个,4 的时候可以再放一个,以此类推。如果从大到小进行遍历,从 10 遍历到 2,那么只能放置一个,不能在前边放置的基础之上,再次进行放置。

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int MaxValue(std::vector<int> &v, std::vector<int> &w, int n, int V) {
    std::vector<int> value(V + 1);
    value.assign(V + 1, 0);
    for (int i = 0; i < n; i++) {
        int tmp_v = v[i];
        int tmp_w = w[i];
        for (int j = tmp_v; j <= V; j++) {
            int value_old = value[j];
            int value_new = value[j - tmp_v] + tmp_w;
            if (value_new > value_old) {
                value[j] = value_new;
            }
        }
    }
    return value[V];
}

int BagFullMaxValue(std::vector<int> &v, std::vector<int> &w, int n, int V) {
    std::vector<int> value(V + 1);
    value.assign(V + 1, -99999999);
    value[0] = 0;
    for (int i = 0; i < n; i++) {
        int tmp_v = v[i];
        int tmp_w = w[i];
        for (int j = tmp_v; j <= V; j++) {
            int value_old = value[j];
            int value_new = value[j - tmp_v] + tmp_w;
            if (value_new > value_old) {
                value[j] = value_new;
            }
        }
    }
    if (value[V] < 0) {
        return 0;
    }
    return value[V];
}

int main() {
    int n = 0;
    int V = 0;
    std::vector<int> v;
    std::vector<int> w;
    cin >> n >> V;
    for (int i = 0; i < n; i++) {
        int a = 0;
        int b = 0;
        cin >> a >> b;
        v.push_back(a);
        w.push_back(b);
    }
    
    int max_value = MaxValue(v, w, n, V);
    int bag_full_max_value = BagFullMaxValue(v, w, n, V);
    std::cout << max_value << std::endl;
    std::cout << bag_full_max_value << std::endl; 
}
相关推荐
橘子遇见BUG2 小时前
算法日记 33 day 动态规划(打家劫舍,股票买卖)
算法·动态规划
格雷亚赛克斯2 小时前
黑马——c语言零基础p139-p145
c语言·数据结构·算法
南宫生2 小时前
力扣-位运算-3【算法学习day.43】
学习·算法·leetcode
Edward The Bunny2 小时前
[算法] 前缀函数与KMP算法
算法
码农多耕地呗2 小时前
区间选点:贪心——acwing
算法
醉酒柴柴2 小时前
【代码pycharm】动手学深度学习v2-08 线性回归 + 基础优化算法
深度学习·算法·pycharm
财富探秘者2 小时前
贵州茅台[600519]行情数据接口
大数据·c语言·python·算法·金融·restful
至善迎风2 小时前
施密特正交化与单位化的情形
线性代数·算法·决策树·机器学习
HABuo3 小时前
【数据结构与算法】合并链表、链表分割、链表回文结构
c语言·开发语言·数据结构·c++·学习·算法·链表
逸风尊者3 小时前
开发也能看懂的大模型:RNN
java·后端·算法