背包问题汇总-01背包、彻底背包、多重背包-java

 

1、01背包

内容:有n件物品和容量为m的背包 给出i件物品的重量以及价值 求解让装入背包的物品重量不超过背包容量 且价值最大 java

特色:每一个物品只有一件供你选择放仍是不放数组

1. 二维解法 优化

    设f[i][j]表示前i件物品 总重量不超过j的最大价值 可得出状态转移方程 :.net

        f[i][j]=max{f[i-1][j-w[i]]+v[i],f[i-1][j]} code

    w[i]:重量数组,v[i]:价值数组blog

    代码以下:rem

// 0,1背包:二维法
	public static int bag1(int W, int[] w, int[] v) {
		int n = w.length - 1;// 第一个值,不算
		int[][] f = new int[n + 1][W + 1];
		for (int i = 1; i <= n; i++)
			for (int j = W; j >= w[i]; j--) 
				f[i][j] = Math.max(f[i - 1][j], f[i - 1][j - w[i]] + v[i]);

		return f[n][W]; // 最优解
	}

    缺点:数据大时,没法处理。get

2.一维解法 博客

    用一维数组代替二维数组就解决了二维数组的问题io

    设f[j]表示重量不超过j公斤的最大价值 可得出状态转移方程 :

        f[j]=max{f[j],f[j−w[i]]+v[i]}

    代码以下:

// 0,1背包:一维法
	public static int bag2(int W, int[] w, int[] v) {
		int n = w.length - 1;// 第一个值,不算
		int[] f = new int[W + 1];
		for (int i = 1; i <= n; i++)
		    for (int j = W; j >= w[i]; j--)
                         f[j] = Math.max(f[j], f[j - w[i]] + v[i]);

		return f[W]; // 最优解

注意:这里第二个for循环为何从大到小?

        由于按正常二维解法,这个 f[i][j]=max{f[i-1][j-w[i]]+v[i],f[i-1][j]} 是要跟上一层i-1比较的,若是一维也用从小到大,不能保证上一层没有遍历过(即不包含vi,每一个只能用一次),这样就乱了,所以要从大到小顺序遍历。

2、彻底背包

内容:有n件物品和容量为m的背包 给出i件物品的重量以及价值 求解让装入背包的物品重量不超过背包容量 且价值最大 

特色: 每一个物品能够无限选用

    有二维和一维两种,但二维的有必定局限 因此只介绍一维方法 

    设f[j]表示重量不超过j公斤的最大价值 可得出状态转移方程 :

        f[j]=max{f[j],f[j−w[i]]+v[i]}

    区别:0,1背包是从大到小遍历,彻底背包是从小到大遍历

    代码以下:

// 彻底背包:一维法-倒序
	public static int bag3(int W, int[] w, int[] v) {
		int n = w.length - 1;// 第一个值,不算
		int[] f = new int[W + 1];
		for (int i = 1; i <= n; i++)
                    for (int j = W; j >= w[i]; j--)         
                        for (int k = 0; j - k*w[i] >=0; k++) 
			    f[j] = Math.max(f[j], f[j - k*w[i]] +k* v[i]);

		return f[W]; // 最优解
	}

优化: 转换成从小到大,由于每一个物品能够用无限次,只要不许超太重量,就不在意以前i(上一层)是否用过。

// 彻底背包:一维法-正序
	public static int bag3(int W, int[] w, int[] v) {
		int n = w.length - 1;// 第一个值,不算
		int[] f = new int[W + 1];
		for (int i = 1; i <= n; i++)
			for (int j = w[i]; j <= W; j++)
				f[j] = Math.max(f[j], f[j - w[i]] + v[i]);
		return f[W]; // 最优解
	}

3、多重背包

内容:有n件物品和容量为m的背包 给出i件物品的重量以及价值 还有数量 求解让装入背包的物品重量不超过背包容量 且价值最大 

特色:每一个物品都有了必定的数量

    设f[j]表示重量不超过j公斤的最大价值 可得出状态转移方程 : 

    f[j]=max{f[j],f[j−k∗w[i]]+k∗v[i]}

    代码以下:

// 多重背包:一维法
	public static int bag4(int W, int[] w, int[] v, int[] num) {
		int n = w.length - 1;// 第一个值,不算
		int[] f = new int[W + 1];
		for (int i = 1; i <= n; i++)
			for (int j = W; j >= w[i]; j--)
				for (int k = 0; k <= num[i] && j - k * w[i] >= 0; k++) {
					f[j] = Math.max(f[j], f[j - k * w[i]] + k * v[i]);
				}

		return f[W]; // 最优解
	}

二进制优化作法: 

        首先,能够把它转化为一个01背包的问题。每一个物品有s件,咱们能够把它差分红s份,每份物品当作不一样的个体,即只能选一次,这就转化为了01背包物品,可是这样的话,复杂度仍是很大。

        继续优化,一个物品的数量是s的话,只要把s拆分红一些数字,使它们可以表示出1-s中任意一个数字,就能够,不必把它拆成s个1。

        那么这样的数字最少须要多少个呢?最少须要log(s)个,向上取整。
好比7,它最少须要3个数字来表示:
即 1(2^0=1 ), 2(2^1=2), 4(2^2=4)。缘由:每一个数字有2种可能选或不选,那么能够表示的不一样数字个数就是 2 * 2 * 2 = 8。可是还须要注意一个问题,就是有些数字可能可以表示出来一些大于s的数字,可是这件物品最多只有s件,那么就须要特殊处理一下最后一个数。
        好比10,若用1,2, 4, 8表示,可能会表示出来大于10的数字,例如:4+8=12。那么若是最后一个数字加上前面数的总和会大于s,就将它替换为剩下的物品个数,即将8替换为3,这时正好能表示出1-s全部的数,-> 1, 2,4能够表示7之内的全部数,这些数加上3就能够表示10之内的全部数啦。
——参考背包九讲

// 多重背包:一维法-二进制优化
	public static int bag4(int W, int[] w, int[] v, int[] num) {
		int n = w.length;
		int[] f = new int[W + 1];
		int[] vv = new int[n*W];
		int[] ww = new int[n*W];
		// 复制w和v
		for(int i=0;i<n;i++)
		{
			vv[i] = v[i];
			ww[i] = w[i];
		}
		k = n;
		for(int i=0;i<n;i++)
		{
			s = num[i]
			for(int i=1;i<=s;i*=2)   //二进制优化
				vv[k]=v[i]*i,ww[k++]=w[i]*i,s-=i;
			if(s>0)
				vv[k]=v[i]*s,ww[k++]=w[i]*s;
		}

		for (int i = 1; i <= k; i++)  //01背包
		    for (int j = W; j >= ww[i]; j--)
                        f[j] = Math.max(f[j], f[j - ww[i]] + vv[i]);

		return f[W]; // 最优解
	}

4、背包问题应用在,数组中找固定和的问题

1. 判断是否有和为100的组合 

 

public static boolean findSum100(int[] a) {
        boolean[] dp = new boolean[101];
        dp[a[0]] = true;
        for (int i = 1; i < a.length; i++) {
            for (int j = 100; j >= a[i]; j--) {
                if (dp[j - a[i]]) {
                    dp[j] = true;
                }
            }
            if (dp[100]) {
                return true;
            }
        }
        return false;
    }

 
2.查找和为100的全部组合

方法一:DFS(2^n)

    public static void dfs(int current, int sum, int[] questions, ArrayList<Integer> path,
            HashSet<List<Integer>> result) {
        if (sum == 100) {
            result.add(new ArrayList<>(path));
            return;
        }
        if (sum > 100) {
            return;
        }
        for (int i = current; i < questions.length; i++) {
            path.add(questions[i]);
            dfs(i + 1, sum + questions[i], questions, path, result);
            path.remove(path.size() - 1);
        }
    }
 
 
  

方法二:动态规划 O(n^2)

// 打印和为n的组合,动规法,O(n^2)
	public static List<List<Object>> findSums(int[] a, int n) {
		boolean[] dp = new boolean[n + 1];
		List<List<Object>>[] list = new ArrayList[n + 1];
		for (int i = 0; i < list.length; i++) {
			list[i] = new ArrayList<>();
		}
		List<Object> temp = new ArrayList<>();
		for (int i = 0; i < a.length; i++) {
			if (a[i] > n) {
				continue;
			}
			for (int j = n; j >= a[i]; j--) {
				if (dp[j - a[i]]) {
					dp[j] = true;
					for (List<Object> arrayList : list[j - a[i]]) {
						temp = new ArrayList<>(arrayList);
						temp.add(a[i]);
						list[j].add(new ArrayList<>(temp));
					}
				}
			}
			dp[a[i]] = true;
			temp.clear();
			temp.add(a[i]);
			list[a[i]].add(new ArrayList<>(temp));
		}
		return list[n];
	}

更多打印的问题:

打印和为sum的组合,动规法+DFS+迭代法 - CSDN博客  :https://blog.csdn.net/qq_19446965/article/details/81775702