算法分析与设计:0-1背包问题(动态规划)【本文算法的时空复杂度都未达到最优,核心目的在于展现并理解动态规划的算法过程】

===== 本文算法的时空复杂度都未达到最优,核心目的在于展现并理解动态规划的算法过程。=====


0-1背包问题

给定 n n n 种物品和一个背包。物品 i i i 的重量为 w i w_i wi,其价值为 p i p_i pi,背包的容积为 c c c。问如何选择装入背包中的物品,使得装入背包中物品的总价值最大?


动态规划

动态规划法是一个分阶段判定决策过程,其问题求解策略的基础是决策过程的最优原理:为达到某问题的最优目标 T T T,需要一次作出决策序列 D = D 1 , D 2 , … , D k D={D_1,D_2,…,D_k} D=D1,D2,,Dk。如果 T T T 是最优的,则对任意 i ( 1 ≤ i ≤ k ) i(1≤i≤k) i(1ik),决策子序列 D ( i + 1 ) , D ( i + 2 ) , … , D k D_(i+1),D_(i+2),…,D_k D(i+1),D(i+2),,Dk 也是最优的,即当前决策的最优性取决于其后续决策序列是否最优。由此追溯至目标,再由最终目标决策向上回溯,导出决策序列 D = D 1 , D 2 , … , D k D={D_1,D_2,…,D_k} D=D1,D2,,Dk。因此动态规划方法可以保证问题求解是全局最优的。


问题分析

此问题的形式化描述是,给定 c > 0 , w i > 0 , p i > 0 ( 1 ⩽ i ⩽ n ) c>0, w_{i}>0, p_{i}>0 (1 \leqslant i \leqslant n) c>0,wi>0,pi>0(1in), 要求找出一个 n n n 元 0-1 向量 ( x 1 , x 2 , ⋯   , x n ) , x i ∈ { 0 , 1 } ( 1 ⩽ i ⩽ n ) , \left(x_{1}, x_{2}, \cdots, x_{n}\right), x_{i} \in\{0,1\} \quad(1 \leqslant i \leqslant n), \quad (x1,x2,,xn),xi{0,1}(1in), 使得 ∑ i = 1 n w i x i ⩽ c , \sum_{i=1}^{n} w_{i} x_{i} \leqslant \mathrm{c}, i=1nwixic, 而且 ∑ i = 1 n p i x i \sum_{i=1}^{n} p_{i} x_{i} i=1npixi 达到最大。因此, 0-1 背包问题是一个特殊的整数规划问题:
max ⁡ ∑ i = 1 n p i x i { ∑ i = 1 n w i x i ⩽ c x i ∈ { 0 , 1 } 1 ⩽ i ⩽ n \max \sum_{i=1}^{n} p_{i} x_{i} \quad\left\{\begin{array}{l} \sum_{i=1}^{n} w_{i} x_{i} \leqslant c \\ x_{i} \in\{0,1\} \quad 1 \leqslant i \leqslant n \end{array}\right. maxi=1npixi{i=1nwixicxi{0,1}1in

递归关系

m ( i , j ) m(i, j) m(i,j) 是背包容量为 j j j, 可选择物品为 i , i + 1 , ⋯   , n i, i+1, \cdots, n i,i+1,,n 时 0-1背包问题的最优值。由 0-1背包问题的最优子结构性质,可以建立计算 m ( i , j ) m(i, j) m(i,j) 的递归式如下:

m ( i , j ) = { max ⁡ { m ( i + 1 , j ) , m ( i + 1 , j − w i ) + p i } j ⩾ w i ( 能 装 进 去 ) m ( i + 1 , j ) 0 ⩽ j < w i ( 装 不 下 ) m ( n , j ) = { p n j ⩾ w n 0 0 ⩽ j < w n \begin{aligned} &m(i, j)=\left\{\begin{array}{ll} \max \left\{m(i+1, j), m\left(i+1, j-w_{i}\right)+p_{i}\right\} & j\geqslant w_i(能装进去) \\ m(i+1, j) & 0 \leqslant j < w_i (装不下) \end{array}\right. \\ &m(n, j)=\left\{\begin{array}{ll} p_{n} & j \geqslant w_{n} \\ 0 & 0 \leqslant j<w_{n} \end{array}\right. \end{aligned} m(i,j)={max{m(i+1,j),m(i+1,jwi)+pi}m(i+1,j)jwi()0j<wi()m(n,j)={pn0jwn0j<wn


C++源代码

#include <iostream>
#include <iomanip>
#include <algorithm>
#define N 100
using namespace std;
int m[N][N] = {0};         // 最优值矩阵:m[i][j] —— 背包容量为j,可选择物品 i,i+1,...,n 时的最优值
int n = 5;                 // 物品数量
int c = 10;                // 背包容量
int p[] = {6, 3, 5, 4, 6}; // 物品价格数组
int w[] = {2, 2, 6, 5, 4}; // 物品重量数组
int x[N];                  // 最优解的物品选择

// 核心函数——生成并储存dp最优值矩阵
void Knapsack_dp()
{
    // 初始化最后一行(从最后往前动态规划)
    for (int j = 0; j <= c; ++j)
    {
        m[n][j] = 0;
        if (j >= w[n])
            m[n][j] = p[n];
    }

    // 进行动态规划的主循环
    for (int i = n - 1; i > 0; i--)
    {
        for (int j = 0; j <= c; ++j)
        {
            if (j < w[i]) // 装不下,背包容量在小于当前物品体积时,把上一行重复的挪下来
                m[i][j] = m[i + 1][j];
            if (j >= w[i]) // 装得下,选择装不装,背包容量大于当前物品体积量时,判断第i个物品是否要装
                m[i][j] = max(m[i + 1][j], m[i + 1][j - w[i]] + p[i]);
        }
    }
    // 最后得出结果的时候进行简化(因为不需要前面的数据为下一个物品做比较)
    m[0][c] = m[1][c];
    if (c >= w[0])
        m[0][c] = max(m[1][c], m[1][c - w[0]] + p[0]);
}

// 根据最优值矩阵,查找最优解的函数
void find_best_solution()
{
    int tp_c = c;   // 设置一个值为背包容量的临时变量
    for (int i = 0; i < n; ++i)
    {
        if (m[i][tp_c] == m[i + 1][tp_c])   // 说明当前容量下这个物品没有选
            x[i] = 0;
        else
        {
            x[i] = 1;
            tp_c -= w[i];
        }
    }
    // 看最后一行有没有选
    x[n] = (m[n][tp_c]) ? 1 : 0;
}

// 打印最优解和最优值
void Print()
{
    cout << "\n动态规划最优值矩阵:" << endl;
    // 输出表头(j),背包容量
    cout << setw(4) << " ";
    for (int i = 0; i <= c; ++i)
    {
        cout << setw(4) << i;
    }
    cout << endl;
    // 输出分割线
    for (int i = -1; i <= c; ++i)
    {
        cout << setw(4) << "---";
    }
    cout << endl;
    for (int i = 0; i < n; ++i)
    {
        cout << setw(3) << i + 1 << "|";
        for (int j = 0; j <= c; ++j)
        {
            cout << setw(4) << m[i][j];
        }
        cout << endl;
    }
    cout << "\n最优值: " << m[0][c] << endl;

    // 输出最优解
    cout << "\n最优解: ";
    for (int i = 0; i < n - 1; ++i)
    {
        cout << x[i] << "-";
    }
    cout << x[n - 1] << endl;
}

int main()
{
    Knapsack_dp();
    find_best_solution();
    Print();
    return 0;
}

程序输出

程序输出

输出展现了0-1背包问题的最优值矩阵,算法从最优值矩阵最后一行(第5行)进入,逐步向上走,每一行的更新都依赖于之前一行(下面的一行)的数据。最优值矩阵的第一行(最后计算的一行)不需要被依赖,因此可以直接简化计算最后一位,得出结果。

图中红色框框标识的是在通过最优值矩阵查找最优解的关键点。

输出的最后是0-1向量,表示了该位置的物品是否选择(1为选择,0为不选择),如图用例选择了①②⑤号物品,此时达到最优值15。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值