您的位置:首页 > 编程语言 > C语言/C++

0-1背包问题 自底向上非递归的动态规划(C++)

2020-06-01 05:12 811 查看

文章目录

  • 二、算法实现
  • 一、关于0-1背包问题

    1. 问题描述:

    • 给定 n 种物品和一个容量为 C 的背包,物品 i 的重量是 wi>0,其价值为 vi>0 。
    • 问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?

    2. 问题分析:

    • ······
    • 有问题的最优子结构,我们可以建立m(i, j)的递归表达式如下:
      m(i,j)={max{m(i+1,j),m(i+1,j−wi)+vi}j≥wim(i+1,j)0≤j≤wi m(i,j)=\begin{cases} max\{m(i+1,j),m(i+1,j-w_i)+v_i\} & j \geq w_i \\ m(i+1,j) & 0 \leq j \leq w_i \end{cases} m(i,j)={max{m(i+1,j),m(i+1,j−wi​)+vi​}m(i+1,j)​j≥wi​0≤j≤wi​​
      m(n,j)={vnj≥wi00≤j≤wi               ⋅ m(n,j)=\begin{cases} v_n & j \geq w_i \\ 0 & 0 \leq j \leq w_i \end{cases}                · m(n,j)={vn​0​j≥wi​0≤j≤wi​​               ⋅
    • 例:假设现在有5件物品,分别重1, 2, 3, 4, 5,分别价值2, 3, 4, 5, 6,背包容量12。
      j1 j2 j3 j4 j5 j6 j7 j8 j9 j10 j11 j12
      i1 0 0 0 0 0 0 0 0 0 0 0 16
      i2 0 3 4 5 7 8 9 10 12 13 14 15
      i3 0 0 4 5 6 6 9 10 11 11 11 15
      i4 0 0 0 5 6 6 6 6 11 11 11 11
      i5 0 0 0 0 6 6 6 6 6 6 6 6
      首先,这张表是至底向上,从左到右的。我们使用 (i5, j3) 表示表的 i5 行 j3 列单元格;
    • 单元格 (i5, j3) 的意义是:子问题“只有物品 i5 时,容量为 j3 的背包所能放入的最大价值为0”,因为容量3的背包放不下重量5的物品;
    • 单元格 (i4, j3) 的意义是:子问题“只有物品 i4 , i5 时,容量为 j3 的背包所能放入的最大价值为0”,因为容量3的背包放不下这两个物品;
    • 其余单元格同理。。。
    • 至此,全表表示了全部子问题的解,单元格 (i1, j12) 即为总问题的解。
  • ······
  • 二、算法实现

    1. 自底向上 非递归 的动态规划

    // 0-1背包问题的自底向上非递归的动态规划
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    
    using namespace std;
    
    int n;		// 物品个数
    int c;		// 背包容量
    int* w;		// 记录每个物品的重量
    int* v;		// 记录每个物品的价值
    
    int* x;		// 记录物品是否被放入背包
    int** m;	// 记录最大价值
    
    void Data_input();
    void MaxValue();
    void OptimalSolution();
    
    int main()
    {
    cout << "0-1背包问题 自底向上非递归的动态规划\n\n";
    
    Data_input();	// 用户输入
    MaxValue();		// 最大价值表
    OptimalSolution();	// 最优解
    
    cout << "\n可选的最大价值是:" << m[1][c] << endl;
    cout << "所选的物品情况是:";
    for (int i = 1;i <= n;i++)
    cout << x[i] << " ";
    cout << endl;
    }
    
    // 用户输入
    void Data_input()
    {
    cout << "请输入物品个数:";
    cin >> n;
    cout << "请输入背包容量:";
    cin >> c;
    
    w = new int[n + 1];
    v = new int[n + 1];
    x = new int[n + 1];
    m = new int* [n + 1];
    for (int i = 0;i <= n;i++)
    {
    m[i] = new int[c + 1];
    memset(m[i], 0, sizeof(int) * (c + 1)); // 将表m所有元素设为0
    }
    
    cout << "请输入物品的重量和价值:\n";
    for (int i = 1;i <= n;i++)
    {
    cout << "#" << i << ":";
    cin >> w[i];
    cin >> v[i];
    }
    }
    
    // 最大价值表
    void MaxValue()
    {
    int jMax = min(w[n] - 1, c);	// 避免存在单个重量超出背包容量的物品使数组m越界
    // 以下两个for循环是最大价值表m的最后一行
    for (int j = 0;j <= jMax;j++)
    m[n][j] = 0;
    for (int j = w[n];j <= c;j++)
    m[n][j] = v[n];
    // 以下是表m最后一行以上的部分
    for (int i = n - 1;i > 1;i--)
    {
    jMax = min(w[i] - 1, c);
    for (int j = 0;j <= jMax;j++)
    // 背包容量<物品重量,即装不下;将m[i+1][j](下一行同列)的值赋给m[i][j]
    m[i][j] = m[i + 1][j];
    for (int j = w[i];j <= c;j++)
    // 背包容量>=物品重量,即可以装下;
    //		放:m[i][j]=m[i + 1][j - w[i]] + v[i]
    //		不放:m[i][j]=m[i + 1][j]
    // 哪个价值大,就采取哪种方式
    m[i][j] = max(m[i + 1][j], m[i + 1][j - w[i]] + v[i]);
    }
    m[1][c] = m[2][c];
    if (c >= w[1])
    m[1][c] = max(m[1][c], m[2][c - w[1]] + v[1]);
    }
    
    // 最优解
    void OptimalSolution()
    {
    int ctemp = c;	// 临时背包容量
    for (int i = 1;i < n;i++)
    {
    if (m[i][ctemp] == m[i + 1][ctemp])
    x[i] = 0;	// 价值相等,说明没有放入这个物品,0
    else
    {
    x[i] = 1;	// 价值不相等,说明放入了这个物品,1
    ctemp = ctemp - w[i];	// 放入了一个物品,背包剩余容量为原容量-物品重量
    }
    }
    x[n] = (m[n][c]) ? 1 : 0;	// 最后一个物品,非0为true放入,0为false不放人
    }

    2. 运行结果展示

    ·

    *其它一些常见算法请参阅此链接~

    最后,非常欢迎大家来讨论指正哦!

    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: