蛮力法解 01 背包问题

本文发表在博客园乌漆 WhiteMoon(https://www.cnblogs.com/linfangnan/),只要不是在博客园看到这篇文章的都是爬虫的哈。

蛮力法

蛮力法也称穷举法或枚举法,是一种简单直接地解决问题的方法,常常直接基于问题的描述,所以蛮力法也是最容易应用的方法。蛮力法所依赖的基本技术是遍历,也称扫描,即采用一定的策略依次理待求解问题的所有元素,从而找出问题的解。依次处理所有元素是蛮力法的关键,为了避免陷人重复试探,应保证处理过的元素不再被处理。

01 背包问题

01 背包是在 N 件物品取出若干件放在可容纳的重量为 W 的背包里,每件物品的重量为 w1, w2, …, wn,与之相对应的价值为 v1, v2, …, vn。01 背包是背包问题中最简单的问题,它的约束条件是给定几种物品,每种物品有且只有一个,并且有价值和重量两个属性。

代码编写

状态表示

首先需要先枚举出所有可能状态,通过迭代遍历这些状态后得出将哪些物品放入背包能获得最大的价值。类似于 IPv4 的地址,使用点分十进制的方式利用一个码串来标识唯一一台网络设备或主机。其中 8 为二进制对应十进制的取值范围为 0 ~ 255,可以描述 256 个状态。

由于每个物品对于背包而言只有放入与否 2 个状态,可以考虑使用 1 位二进制来表示,1 表示放入,0 表示不放入。对于 N 个物品,可以用一个 N 位的二进制码串来说明哪些物品放入背包。假设二进制的高位表示物品序号较低的物品,低位表示物品序号较高的物品,如下是当 N = 4 时的一些状态表示的含义。

0000:所有物品都不放入
0001:放入物品 4
0010:放入物品 3
1100:放入物品 1、2
1111:4 个物品全部放入

一个二进制码串可以使用顺序表进行简单实现,状态需要变换时只需要对最低位加一,然后依次判断每一位的状态。若低位等于 2,就需要向高位进 1 后归为 0,由低位向高位遍历即可完成状态转换。例如此处我使用 C++ 的 vector 进行实现,向量 state 的初始状态为 0000,最终状态为 1111。

vector<int> state(items_num, 0);    // 某个状态放入的物品
// 设二进制码串位数为 n,则需要经过 2^n 次迭代 
for(int i = 1; i < pow(2, items_num); i++)
{
    //更新当前状态
    state[0] += 1;    // 最低位加 1
    for(int j = 0; j < items_num; j++)
    {
        // 逢 2 进位
        if(state[j] == 2)
        {
            state[j] = 0;
            state[j + 1] += 1;
        }
    }
}

约束条件

现在我们已经能描述背包放入物品的所有状态了,接下来只需要结合约束条件判断最优解即可。为了记录最优状态和最大的价值量,我们需要定义一些变量来记录,如下是我用到的一些变量和用来判断是否需要更新状态的分支结构。

int max_value = 0;    // 记录最大价值 
int now_weight;       // 某个状态时的重量 
int now_value;        // 某个状态的价值 
vector<int> max_state(state);       // 最大价值时放入的物品 
vector<int> state(items_num, 0);    // 某个状态放入的物品

// 判断这种取法是否没超过容量且价值更高,是就更新最佳状态
if(now_weight <= pack_capacity && now_value > max_value)
{
    max_value = now_value;
    max_state.assign(state.begin(), state.end());    // 复制vector容器 
}

完整代码

综合上述的思想,加上输入输出和过程处理的其他代码,得到的完整代码如下。

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
int main()
{
    // 输入物品数量和背包容量 
    int items_num;        //物品数量 
    int pack_capacity;    //背包容量 
    cout << "物品的数量为:"; 
    cin >> items_num;
    cout << "背包的容量为:"; 
    cin >> pack_capacity;
	
    // 输入每个物品的重量和价值 
    vector<int> weights(items_num);
    vector<int> values(items_num);
    for(int i = 0; i < items_num; i++)
    {
        cout << "物品" << i << "的重量为:";
        cin >> weights[i];
        cout << "物品" << i << "的价值为:";
        cin >> values[i];
    }
    
    // 蛮力法求解01背包问题 
    int max_value = 0;    // 记录最大价值 
    int now_weight;       // 某个状态时的重量 
    int now_value;        // 某个状态的价值 
    vector<int> max_state(state);       // 最大价值时放入的物品 
    vector<int> state(items_num, 0);    // 某个状态放入的物品
    // 设物品数量为 n,则需要经过 2^n 次迭代 
    for(int i = 1; i < pow(2, items_num); i++)
    {
        now_weight = 0;
        now_value = 0;
        //更新当前状态,并计算该状态下的价值 
        state[0] += 1;
        for(int j = 0; j < items_num; j++)
        {
            // 逢2进位,1表示放入背包,0表示不放入(不处理) 
            if(state[j] == 2)
            {
                state[j] = 0;
                state[j + 1] += 1;
            }
            else if(state[j] == 1)
            {
                now_weight += weights[j];
                now_value  += values[j];
            }
        }
        // 判断这种取法是否没超过容量且价值更高,是就更新最佳状态
        if(now_weight <= pack_capacity && now_value > max_value)
        {
            max_value = now_value;
            max_state.assign(state.begin(), state.end());    // 复制vector容器 
        }
    }
	
    // 输出结果 
    cout << "\n该背包能存放物品的最大价值为:" << max_value << endl;
    cout << "物品分别是:";
    for(int i = 0; i < items_num; i++)
    {
        if(max_state[i] == 1)
        {
            cout << "物品" << i << " "; 
        }
    }
    return 0;
}

对于一个具有 n 个元素的集合,总共会有 2^n 种物品取法,因此无论表示状态和改变状态的算法多么高明,都需要依次检查这 2^n 种状态,时间复杂度为 O(2^n)。

测试数据

测试数据 1

4 个物品,重量和质量信息如下,容量为 10 的背包。

物品序号 重量 质量
0 7 42
1 3 12
2 4 40
3 5 25

测试数据 2

8 个物品,重量和质量信息如下,容量为 25 的背包。

物品序号 重量 质量
0 7 42
1 5 31
2 3 12
3 2 7
4 4 40
5 8 41
6 6 25
7 9 40

posted @ 2022-10-15 21:06  乌漆WhiteMoon  阅读(1241)  评论(0编辑  收藏  举报