基于动态规划方法求解0-1背包问题

这篇博客详细介绍了如何使用动态规划方法解决01背包问题,包括问题描述、算法设计、正确性证明、复杂度分析及算法实现。动态规划算法通过构建二维数组存储中间解,确保了无后效性和最优子结构,从而找到背包中物品的最大总价值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基于动态规划方法求解0-1背包问题

问题描述

现有n个物品,1个背包。对物品i,其价值为viv_ivi ,重量为WiW_iWi,背包的容量为WWW,如何选取物品使得背包里转入物品的总价值最大?

在约束条件为:选取物品的重量小于等于背包重量的情况下,尽可能让背包中物品的总价值最大。

算法设计

根据问题描述,约束条件为,目标函数为:

在满足约束条件的情况下,找到是目标函数最大的解。

使用两个等长的一维数组存储物品的重量weights[n]weights[n]weights[n]和价值values[n]values[n]values[n],这里第iii个物品的重量就是weights[i]weights[i]weights[i],价值就是values[i]values[i]values[i]

使用动态规划的方式求解该问题,构建二维数组dp[n+1][W+1]dp[n+1][W+1]dp[n+1][W+1]来记录中间过程的最优解,这里dp[i][j]dp[i][j]dp[i][j]表示当背包容量为jjj时,装前iii个物品的最优解。

描述算法

依次考虑前i−1i-1i1个物品装入背包的最优解,那么装入前iii个物品的最优解就是在前i−1i-1i1个物品的最优解上进行构建

如果只考虑第i件物品放还是不放,那么就可以转化为一个只涉及到前i-1个物品的问题。如果不放第i个物品,那么问题就转化为“前i-1件物品放入容量为j的背包中的最优价值组合”,对应的值为dp[i−1,j]dp[i-1,j]dp[i1,j]。如果放第i个物品,那么问题就转化成了“前i-1件物品放入容量为j−wij-w_ijwi的背包中的最优价值组合”,此时对应的值为dp[i−1,j−Wi)]+vidp[i-1,j-W_i)]+v_idp[i1,jWi)]+vi

  • dp[i][0]=dp[0][j]=0dp[i][0]=dp[0][j]=0dp[i][0]=dp[0][j]=0

  • 如果第iii个 物品重量大于背包总重量jjj,那么dp[i][j]=dp[i−1][j]dp[i][j]=dp[i-1][j]dp[i][j]=dp[i1][j]

  • 如果第iii个 物品重量小于等于背包总重量jjj,那么dp[i][j]=max(dp[i−1][j],dp[i−1][j−weights[i]]+values[i]dp[i][j]=max(dp[i-1][j],dp[i-1][j-weights[i]]+values[i]dp[i][j]=max(dp[i1][j],dp[i1][jweights[i]]+values[i]

    01PACKAGE(values[n],weights[n],W) // W为背包容量
    	dp[n+1][W+1]
    	for i=0 to n:
    		do dp[i][0] = 0
    	for j=0 to W:
    		do dp[0][j] = 0
    	for i=1 to n:
    		for j=1 to W:
    			do
    			if j < weights[i]:
    				dp[i][j]=dp[i-1][j]
    			else
    				dp[i][j]=
    					max(dp[i-1][j],dp[i-1][j-weights[i]]+values[i])
    	return C
    

算法的正确性证明

假设(x1,x2,…,xn)(x_1,x_2,…,x_n)(x1x2xn)是01背包问题的最优解,则有(x2,x3,…,xn)(x_2,x_3,…,x_n)(x2x3xn)是其子问题的最优解,假设(y2,y3,…,yn)(y_2,y_3,…,y_n)(y2y3yn)是上述问题的子问题最优解,则有(v2y2+v3y3+…+vnyn)+v1x1>(v2x2+v3x3+…+vnxn)+v1x1(v_2y_2+v_3y_3+…+v_ny_n)+v_1x_1 > (v_2x_2+v_3x_3+…+v_nx_n)+v_1x_1(v2y2+v3y3++vnyn)+v1x1>(v2x2+v3x3++vnxn)+v1x1。说明(X_1,Y_2,Y_3,…,Y_n)才是该01背包问题的最优解,这与最开始的假设(X_1,X_2,…,X_n)是01背包问题的最优解相矛盾,故01背包问题满足最优性原理

至于无后效性,其实比较好理解。对于任意一个阶段,只要背包剩余容量和可选物品是一样的,那么我们能做出的现阶段的最优选择必定是一样的,是不受之前选择了什么物品所影响的。即满足无后效性

算法复发性分析

时间复杂度:

空间复杂度:

算法实现与测试

public class Main {
    public static void main(String[] args) {
        int weights[] = {2, 6, 3, 4, 2, 8, 2, 4, 7, 5, 1};
        int values[] = {10,23,5,34,23,17,22,32,12,15,32};
        int W=15;

        int dp[][] = packet01(W,weights,values);
        int res[] = new int[W];
        int j = W;
        for (int i = weights.length; i > 0; i--) {
            if(dp[i][j] == dp[i-1][j])
                res[i-1] = 0;
            else{
                res[i-1] = 1;
                j -= weights[i-1];
            }
        }
        System.out.println("res: "+dp[weights.length][W] );
        System.out.println("packets: ");
        for (int i = 0; i < W; i++) {
            System.out.print(res[i] +" ");
        }

    }
    public static int[][] packet01(int W, int weights[], int values[]) {
        int n = weights.length;
        int [][]dp = new int[n+1][W+1];

        for (int i = 0; i <= n; i++) {
            dp[i][0] = 0;
        }
        for (int j = 0; j <= W ; j++) {
            dp[0][j] = 0;
        }

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= W; j++) {
                int currentItem = i-1;
                if(j < weights[currentItem]){
                    dp[i][j] = dp[i-1][j];
                } else {
                    dp[i][j] = Math.max(dp[i-1][j],
                            dp[i-1][j-weights[currentItem]] + values[currentItem]);
                }
            }
        }
        return dp;
    }
}

测试:

res: 153
packets: 
1 0 0 1 1 0 1 1 0 0 1 0 0 0 0 

心得体会

动态规划算法通常是用来解决某种最优性质的问题。基本思想是将带求解问题划分为若干个子问题,先求解子问题,然后从子问题的解得到原问题的解。动态规划与分治法的区别在于,动态规划的子问题可能是互相重叠的重复计算的,分治法则是相互独立的。可以用一个表来记录子问题是否已经求解,这样可以避免重复求解。

动态规划需要满足:

  • 最优化原理,一个最优化策略的子策略一定是最优的,就是满足最优子结构的性质。

  • 无后效性,一个阶段以前各阶段的状态无法直接硬性它未来的决策,只能通过当前的这个状态。

  • 重叠性,就是记录已经解决过的问题,需要存储已经解决过的问题,空间复杂度比较大,是一种以空间换时间的算法。

动态规划的难点在于,如何根据问题的最优子结构的性质,构造动态规划方法中的递归公式或动态规划方程。就比如本问题中,如何设计这个方程才是难点所在。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值