必威体育Betway必威体育官网
当前位置:首页 > IT技术

彻底理解0-1背包问题

时间:2019-06-04 07:45:09来源:IT技术作者:seo实验室小编阅读:62次「手机版」
 

0-1背包问题

0-1背包问题

给定n个重量为w1w_1w1​,w2w_2w2​,w3w_3w3​,…,wnw_nwn​,价值为v1v_1v1​,v2v_2v2​,v3v_3v3​,…,vnv_nvn​的物品和容量为CCC的背包,求这个物品中一个最有价值的子集,使得在满足背包的容量的前提下,包内的总价值最大

0-1背包问题指的是每个物品只能使用一次

递归方法

首先我们用递归的方式来尝试解决这个问题

我们用F(n,C)F(n,C)F(n,C)表示将前nnn个物品放进容量为CCC的背包里,得到的最大的价值。

我们用自顶向下的角度来看,假如我们已经进行到了最后一步(即求解将nnn个物品放到背包里获得的最大价值),此时我们便有两种选择

  1. 不放第nnn个物品,此时总价值为F(n1,C)F(n-1,C)F(n−1,C)
  2. 放置第nnn个物品,此时总价值为vn+F(n1,Cwn)v_n+F(n-1,C-w_n)vn​+F(n−1,C−wn​)

两种选择中总价值最大的方案就是我们的最终方案,递推式(有时也称之为状态转移方程)如下

F(i,C)=max(F(i1,C),v(i)+F(i1,Cw(i)))F(i,C)=max(F(i-1,C),v(i)+F(i-1,C-w(i)))F(i,C)=max(F(i−1,C),v(i)+F(i−1,C−w(i)))

编程实现如下:

public class KnapSack01 {
    /**
     * 解决背包问题的递归函数
     *
     * @param w        物品的重量数组
     * @param v        物品的价值数组
     * @param index    当前待选择的物品索引
     * @param capacity 当前背包有效容量
     * @return 最大价值
     */
    private static int solveKS(int[] w, int[] v, int index, int capacity) {
        //基准条件:如果索引无效或者容量不足,直接返回当前价值0
        if (index < 0 || capacity <= 0)
            return 0;

        //不放第index个物品所得价值
        int res = solveKS(w, v, index - 1, capacity);
        //放第index个物品所得价值(前提是:第index个物品可以放得下)
        if (w[index] <= capacity) {
            res = Math.max(res, v[index] + solveKS(w, v, index - 1, capacity - w[index]));
        }
        return res;
    }

    public static int knapSack(int[] w, int[] v, int C) {
        int size = w.length;
        return solveKS(w, v, size - 1, C);
    }

    public static void main(String[] args){
        int[] w = {2,1,3,2};
        int[] v = {12,10,20,15};
        System.out.println(knapSack(w,v,5));
    }
}

记忆化搜索

我们用递归方法可以很简单的实现以上代码,但是有个严重的问题就是,直接采用自顶向下的递归算法会导致要不止一次的解决公共子问题,因此效率是相当低下的。

我们可以将已经求得的子问题的结果保存下来,这样对子问题只会求解一次,这便是记忆化搜索。

下面在上述代码的基础上加上记忆化搜索

public class KnapSack01 {
    private static int[][] memo;

    /**
     * 解决背包问题的递归函数
     *
     * @param w        物品的重量数组
     * @param v        物品的价值数组
     * @param index    当前待选择的物品索引
     * @param capacity 当前背包有效容量
     * @return 最大价值
     */
    private static int solveKS(int[] w, int[] v, int index, int capacity) {
        //基准条件:如果索引无效或者容量不足,直接返回当前价值0
        if (index < 0 || capacity <= 0)
            return 0;

        //如果此子问题已经求解过,则直接返回上次求解的结果
        if (memo[index][capacity] != 0) {
            return memo[index][capacity];
        }

        //不放第index个物品所得价值
        int res = solveKS(w, v, index - 1, capacity);
        //放第index个物品所得价值(前提是:第index个物品可以放得下)
        if (w[index] <= capacity) {
            res = Math.max(res, v[index] + solveKS(w, v, index - 1, capacity - w[index]));
        }
        //添加子问题的解,便于下次直接使用
        memo[index][capacity] = res;
        return res;
    }

    public static int knapSack(int[] w, int[] v, int C) {
        int size = w.length;
        memo = new int[size][C + 1];
        return solveKS(w, v, size - 1, C);
    }

    public static void main(String[] args) {
        int[] w = {2, 1, 3, 2};
        int[] v = {12, 10, 20, 15};
        System.out.println(knapSack(w, v, 5));
    }
}

动态规划算法

在这里插入图片描述

在这里插入图片描述

public class KnapSack01 {
    public static int knapSack(int[] w, int[] v, int C) {
        int size = w.length;
        if (size == 0) {
            return 0;
        }

        int[][] dp = new int[size][C + 1];
        //初始化第一行
        //仅考虑容量为C的背包放第0个物品的情况
        for (int i = 0; i <= C; i++) {
            dp[0][i] = w[0] <= i ? v[0] : 0;
        }
		//填充其他行和列
        for (int i = 1; i < size; i++) {
            for (int j = 0; j <= C; j++) {
                dp[i][j] = dp[i - 1][j];
                if (w[i] <= j) {
                    dp[i][j] = Math.max(dp[i][j], v[i] + dp[i - 1][j - w[i]]);
                }
            }
        }
        return dp[size - 1][C];
    }

    public static void main(String[] args) {
        int[] w = {2, 1, 3, 2};
        int[] v = {12, 10, 20, 15};
        System.out.println(knapSack(w, v, 5));
    }
}

空间复杂度的极致优化

上面的动态规划算法使用了O(n*C)的空间复杂度(因为我们使用了二维数组来记录子问题的解),其实我们完全可以只使用一维数组来存放结果,但同时我们需要注意的是,为了防止计算结果被覆盖,我们必须从后向前分别进行计算

在这里插入图片描述

我们仍然假设背包空间为5,根据F(i,C)=max(F(i1,C),v(i)+F(i1,Cw(i)))F(i,C)=max(F(i-1,C),v(i)+F(i-1,C-w(i)))F(i,C)=max(F(i−1,C),v(i)+F(i−1,C−w(i)))我们可以知道,当我们利用一维数组进行记忆化的时候,我们只需要使用到当前位置的值和该位置之前的值,举个例子

假设我们要计算F(i,4)F(i,4)F(i,4),我们需要用到的值为F(i1,4)F(i-1,4)F(i−1,4)和F(i1,4w(i))F(i-1,4-w(i))F(i−1,4−w(i)),因此为了防止结果被覆盖,我们需要从后向前依次计算结果

最终的动态规划代码如下

public class KnapSack01 {
    public static int knapSack(int[] w, int[] v, int C) {
        int size = w.length;
        if (size == 0) {
            return 0;
        }

        int[] dp = new int[C + 1];
        //初始化第一行
        //仅考虑容量为C的背包放第0个物品的情况
        for (int i = 0; i <= C; i++) {
            dp[i] = w[0] <= i ? v[0] : 0;
        }

        for (int i = 1; i < size; i++) {
            for (int j = C; j >= w[i]; j--) {
                dp[j] = Math.max(dp[j], v[i] + dp[j - w[i]]);
            }
        }
        return dp[C];
    }

    public static void main(String[] args) {
        int[] w = {2, 1, 3, 2};
        int[] v = {12, 10, 20, 15};
        System.out.println(knapSack(w, v, 5));
    }
}

利用背包问题的思想解决问题

leetcode 416 Partition Equal Subset Sum

给定一个仅包含正整数的非空数组,确定该数组是否可以分成两部分,要求两部分的和相等

问题分析

该问题我们可以利用背包问题的思想进行求解。

假设给定元素个数为nnn的数组arr,数组元素的和为sum,对应于背包问题,等价于有nnn个物品,每个物品的重量和价值均为为arr[i],背包的限重为sum/2,求解背包中的物品最大价值为多少?

class Solution {
    private boolean knapSack(int[] nums,int sum){
        int size = nums.length;
        
        boolean[] dp = new boolean[sum + 1];
        for (int i = 0;i <= sum;i ++){
           dp[i] = i == nums[0];
        }
        
        for (int i = 1;i < size;i++){
            for (int j = sum;j >= nums[i];j--){
                dp[j] = dp[j] || dp[j-nums[i]];
            }
        }
        return dp[sum];
    }
    
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int item : nums){
            sum += item;
        }
        
        //如果数组元素和不是2的倍数,直接返回false
        if (sum % 2 != 0)
            return false;
        
        return knapSack(nums,sum/2);
    }
}

相关阅读

背包问题总结

前面是转载来的背包9讲,非常详细,后面有几个lintcode上的题目 前言 本篇文章是我(dd_engi)正在进行中的一个雄心勃勃的写作计划的一

01背包问题(记忆型的递归)

1. 问题描述: 有n个重量和价值分别为wi,vi的物品,从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。1≤n

完全背包问题的动态规划解法(普通的思路)

1. 问题描述: 有n个重量和价值分别为wi,vi的物品,从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值1≤n≤10

0-1背包问题、贪心算法、动态规划

1、0-1背包问题  0-1背包问题:有一个贼在偷窃一家商店时,发现有n件物品,第i件物品价值vi元,重wi磅,此处vi与wi都是整数。他希望带走

背包问题 (动态规划算法)

声明:原文出处:https://blog.csdn.net/xp731574722/article/details/70766804 0-1 背包问题:给定 n 种物品和一个容量为 C 的背包,

分享到:

栏目导航

推荐阅读

热门阅读