• 动态规划算法三:0-1背包问题


    一、算法分析

    1、问题描述:给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为C。问:应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
    输入:集合W={w1,w2,...,wn}, V={v1,v2,...,vn}, 数值C
    输出:向量X={x1,x2,...,xn}, xi=1表示物品i放入包中,xi=0表示不放入包中。Σxiwi ≤ C,且Σxivi最大

    2、分析:(寻找最优子结构,转移矩阵,递归关系公式)
    假设y = {y1, y2,...,yi}是W,V,数值为j的0-1背包问题的一个最优解(最优解可能不止一个)

    (1)若wi > j, 则y` = {y1, y2,...,yi-1}为W={w1,w2,...,wi-1}, V={v1,v2,...,v-1}, 数值为j的子问题的最优解(yi必定为0,物品i肯定不能放入背包中);

    (2)若wi <= j, 且yi = 0,则y` = {y1, y2,...,yi-1}为W={w1,w2,...,wi-1}, V={v1,v2,...,v-1}, 数值为j的子问题的最优解(yi当前值为0,物品i经过权衡后,决定不放入背包中);

    (3)若wi <= j, 且yi = 1,则y` = {y1, y2,...,yi-1}为W={w1,w2,...,wi-1}, V={v1,v2,...,v-1}, 数值为j - wi的子问题的最优解(yi当前值为1,物品i放入背包中);

    以上(1)、(2)结论,显然成立,可以用反证法证明结论(3):

    3、由以上分析,转移矩阵如下:

    [m[i,j]= egin{cases} 0& ext{i=0或j=0}\ m[i-1,j]& ext{i>0且wi>j}\ maxlbrace v_i + m[i-1,j-w_i], m[i-1,j] brace & ext{i>0且wi <= j} end{cases}]

    其中,i表示物品数量,j表示背包重量限制,m[i,j]的值表示对应场景下,背包可以装取货区的最大价值,即目标值。

    4、算法步骤:
    (1)数据设置:有以上分析,需要遍历物品数量与背包重量,用二维数组记录目标值;
    (2)初始化:当数量为0或者背包重量限制为0时,目标值为0(对应转移矩阵的初始情况);
    (3)遍历i和j:根据判断条件以及历史记录,计算出新值

    5、获取结果:
    根据背包价值矩阵,判断物品数量变动时,目标值是否变动,以此确定该物品是否需要装入背包。

    二、代码实现

    1、构建背包价值矩阵:

    // w表示各物品重量,v表示各物品价值,n表示物品数量,c表示背包重量限制
    int *knapsack(int *w, int *v, int n, int c)
    {
        // 内存申请,将一维矩阵当做二维矩阵使用,行优先
        int *m = (int *)malloc((n + 1) * (c + 1) * sizeof(int));
        int i, j;
        // 初始化
        for (i = 1; i < n + 1; i++) {
            m[i * (c + 1)] = 0;
        }
        for (j = 0; j < c + 1; j++) {
            m[j] = 0;
        }
        
        // 遍历物品数量i与重量j限制,填写背包价值矩阵
        // 背包矩阵中,添加了i=0和j=0时的初始值,导致m[i,j]对应的物品重量为w[i-1],价值为v[i-1]
        for (i = 1; i <= n; i++) {
            for (j = 1; j <= c; j++) {
                // 在填写m[i,j]时,实际上是确认第i-1各物品是否需要放入背包
                // 若当前物品重量w[i-1]大于背包总体重量时,必定不能放入,目标值与分析上一个物品时的目标值(m[i-1,j])相同
                m[i * (c + 1) + j] = m[(i - 1) * (c + 1) + j];
                // 若当前物品重量小于背包总体重量,需要重点考虑
                if (w[i - 1] <= j) {
                    // 若该物品的重量小于物品总体重量,且当前物品价值v[i-1] + 原放弃一个物品且保证其重量不超出时对应的目标值m[i-1,j-wi]
                    //  > 不放入该物品是的目标值m[i,j]时,选择将该物品放入背包中,此时可以保证重量与总价值均为最优
                    if (v[i - 1] + m[(i - 1) * (c + 1) + j - w[i - 1]] > m[(i - 1) * (c + 1) + j]) {
                        // 将该物品价值v[i-1]+背包中重量减去当前物品重量时且物品个数少一个时对应的目标值,并将其更新到m[i,j]中
                        // 确保重量限制于物品个数限制,m中记录的均为对应场景下的最优值
                        m[i * (c + 1) + j] = v[i - 1] + m[(i - 1) * (c + 1) + j - w[i - 1]];
                    }
                }
            }
        }
    
        return m;
    }
    
    

    2、根据价值矩阵,获取结果:

    // m为背包价值矩阵,n为物品数量,w为物品重量,c为背包重量限制
    int *bulidSolution(int *m, int n, int *w, int c)
    {
        int i;
        int j = c;
        int *x = (int*)malloc(n * sizeof(int));
        for (i = n; i >= 1; i--) {
            // 判断物品个数变化时,目标值是否变化
            if (m[i * (c + 1) + j] == m[(i - 1) * (c + 1) + j]) {
                // 不变,该物品不放入背包
                x[i - 1] = 0;
            } else {
                // 变化,物品放入背包,且更新背包重量j
                x[i - 1] = 1;
                j -= w[i - 1];
            }
        }
    
        return x;
    }
    
    

    三、测试结果

    测试程序:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    int main(void)
    {
        int w[] = {2, 3, 4, 5};
        int v[] = {3, 4, 5, 6};
        int *m, *x;
        int thingsNum = sizeof(w) / sizeof(w[0]);
        int maxWeight = 9;
        
        // knapsack函数实现参考上一节内容
        m = knapsack(w, v, thingsNum, maxWeight);
    
        // 打印价值矩阵m
        printf("knapsackTab start: 
    
    ");
        for (int i = 0; i < thingsNum + 1; i++) {
            for (int j = 0; j < maxWeight + 1; j++) {
                printf(" %2d ", m[i * (maxWeight + 1) + j]);
            }
    
            printf("
    
    ");
        }
        printf("knapsackTab end. 
    ");
    
        // bulidSolution函数实现参考上一节内容    
        x = bulidSolution(m, thingsNum, w, maxWeight);
        // 输出物品装包策略:0-不放,1-放入
        printf("
    ans[] = ");
        for (int i = 0; i < thingsNum; i++) {
            printf("%2d ", x[i]);
        }
    
        while (1);
        return 0;
    }
    
    

    测试结果:

    四、leetcode题

    待分析
    
    
  • 相关阅读:
    虚拟DOM和diff算法
    面向对象之封装
    面向对象之类和函数的属性
    面向对象之__init__方法
    面向对象之初始类和对象
    面向对象与面向过程详解
    CSS高级技巧
    CSS定位
    模块之re模块详解
    模块之logging模块详解
  • 原文地址:https://www.cnblogs.com/HZL2017/p/14612995.html
Copyright © 2020-2023  润新知