从论证最优子结构与重叠子问题开始的01背包问题

发布时间 2023-11-07 16:21:52作者: lmj625

原创声明:本文所有图片和代码皆由本人制作和编写。

前言

网上关于01背包动态规划的题解很多,重心大多都放在了状态转移方程上,而对于动态规划算法必须具备的2个特征并没有进行论证与阐述,本篇文章将从论证最优子结构与重叠子问题开始,利用动态规划法来解决01背包问题。



定义子问题

有n件物品和一个最多能背重量为w的背包。每件物品只能装入背包一次,求解将哪些物品装入背包里物品价值总和最大。


论证最优子结构

设(X1,X2, ... ,Xn)是0/1背包问题的最优解,证明(X2, ... ,Xn)是子问题:image的最优解,其中,子问题有约束条件:
image

反证法:设子问题另有最优解(Y2, ... ,Yn)。
那么有:image
所以有:image
由子问题的第一个约束条件移项得:image
综上,推出0/1背包问题的最优解是(X1,Y2, ... ,Yn), 而这与题设矛盾,故假设不成立,即子问题不存在另外的最优解,所以(X2, ... ,Xn)就是子问题的最优解,0/1背包问题具备最优子结构。



阐述重叠子问题

对于第i个物品只有两种可能,能装与不能装。
不能装时,所求结果与“装前i-1个物品最优决策结果”一样,即重叠,不必重复计算。

所以,0/1背包问题具备最优子结构以及重叠子问题,可以考虑动态规划解法。



动态规划函数

继续考虑能装时的情况,所求结果在 “第i个物品价值 + 为第i个物品预留空间后的装前i-1个物品最优决策结果” 与 “不为第i个物品预留空间的装前i-1个物品最优决策结果”之间取最大值。
综上可设计动态规划函数:
设V(i,j)表示把前i个物品放入容量为j的背包可获得的最优结果。
则有递推式如下:
image
显然,只有当V(i,j) > V(i-1,j)时,第i件物品被放入了背包,否则,则没有放入。
故有:
image



算法流程图

image



算法实现

public int solveBag(){
	dpTable=new int[objectsNum+1][weightLimit+1];
        /*把第i个物品放入当前容量为0的背包*/
        for(int i=0 ;i < dpTable.length ; i++){
            dpTable[i][0] = 0;
        }
        /*把第0个物品放入当前容量为i的背包*/
        Arrays.fill(dpTable[0], 0);

        taken = new int[objectsNum];
        Arrays.fill(taken,0);
        for(int objectIndex = 1; objectIndex <= objectsNum; objectIndex++){
            for(int space=1; space <= weightLimit ; space++){
                if(objectsWeight[objectIndex - 1] > space){
                    dpTable[objectIndex][space] = dpTable[objectIndex-1][space];
                }else{
                    /*空间够,决策:空间要不要腾给当前物品,
                    比较:全部空间给前面物品的最优解 和
                         当前物品的价值 + 剩余空间给前面物品的最优解
                    取最大值*/
                    dpTable[objectIndex][space] =
                            Math.max(dpTable[objectIndex - 1][space],
                                    dpTable[objectIndex - 1][space - objectsWeight[objectIndex - 1]] + objectsValue[objectIndex - 1]);
                }
            }
        }
        maxWeight = dpTable[objectsNum][weightLimit];

        //回溯,查看每一件物品是否被装
        for(int i = objectsNum,space = weightLimit ; i>0 ; i--){
            if(dpTable[i][space] > dpTable[i-1][space]){
                space -= objectsWeight[i-1];
                taken[i-1] = 1;
                takenNum++;
            }
        }

        return maxWeight;
    }


时空复杂度

时间复杂度

01背包动态规划解法的时间复杂度主要是在双层for循环遍历二维数组填表。所以时间复杂度应该为O(nm),其中n为物品个数,m为背包容量。

空间复杂度

主要的空间复杂性在于储存计算结果的二维数组表,所以空间复杂度为O(nm),其中n为物品个数,m为背包容量。

后记

感谢你看到这里。