算法基础四:动态规划---0-1背包问题

算法基础四:动态规划---0-1背包问题

一、算法描述与分析

1、问题的理解与描述

问题理解

image-20211005122142263

问题描述

image-20211005122212399

2、解题思路

image-20211005140844320

①思路

image-20211005141845274

②状态转移方程

f(k,w):当背包容量为w,现有k件物品可以偷所能偷到的最大价值。

image-20211005142218077

③表格(图示)

image-20211005142328958

解释:

  • 第一行和第一列为0,因为当背包容量为0的时候,不论还有几件物品可以偷,那么价值都是0,偷不到。如果剩下0件物品可以偷,那么价值也是0。相当于初始化。
  • 比如f(1,2),意思就是背包容量还剩下2,现在还有一件物品可以偷,这个物品的重量为2,价值为3。或者带入转移方程也可以看出来,偷与不偷的情况。

二、算法的伪代码描述

1.算法伪代码

KNAPSACK(v, w, C)
	n <- length[v]
	for j <- 0 to C
		do f[0,j] <-0 #j控制容量
	for i<-1 to n
		do f[i,0] <-0
	for j<-1 to C
		do f[i,j] <- f[i-1,j]
			if wi <= j
				then if vi + f[i-1,j-wi] > f[i-1,j
					then f[i,j] <- vi + f[i-1,j-wi
	return  f

2、构造一个最优解

利用KNAPSACK返回的矩阵m,可以构造出如下最优解:

BUILD-SOLUTION(f,w,C)
	n <- length[w]
	j <- C
	for i<- n to 1
		do if f[i,j] = f[i-1,j]
				then x[i] <- 0
			else 
				x[i] <- 1
				j <- j-w[i]#如果拿了的话,容量减少
	return x

三、代码实现

1、算法代码

public class Knapsack {
    public static int[][] knapsack(int[] w,int[] v,int c){
        int i,j,n = w.length;
        int [][] f = new int[n+1][c+1];
        for (i=1;i<n+1;i++)
            f[i][0] = 0;
        for (j=0;j<c+1;j++)
            f[0][j] = 0;
        for (i=1;i<=n;i++)
            for (j=1;j<=c;j++){
                f[i][j] = f[i-1][j];//默认其太重放不下。或者是不拿当前这个物品的情况。
                if (w[i-1]<=j)//此种情况就是如果当前物品能放下
                    if (v[i-1]+f[i-1][j-w[i-1]]>f[i-1][j])//并且拿了这个物品的总价值要大于不拿这个物品
                        f[i][j] = v[i-1]+f[i-1][j-w[i-1]];
            }
        return f;
    }

    public static int[] buildSolution(int[][] f,int[] w,int c){
        int i,j=c,n=w.length;
        int[] x = new int[n];
        for (i=n;i>=1;i--)
            if (f[i][j] == f[i-1][j])//说明当前这个物品,可能是没拿,也有可能是放不下。
                x[i-1] = 0;//那么就说明这个物品没拿
            else {//否则就说拿了,需要减去这个重量,重新的去寻找下一个拿了的物品。
                x[i-1] = 1;
                j -= w[i-1];
            }

        return x;
    }

}

2、测试代码

public class Test {
    public static void main(String[] args) {
        int w[] = {2,3,4,5},v[]={3,4,5,7};
        int[][] f;
        int[] x;
        f = Knapsack.knapsack(w,v,9);
        x = Knapsack.buildSolution(f,w,9);
        for (int i = 0; i < 4; i++) {
            System.out.print(x[i]+" ");
        }
        System.out.println();
    }
}

四、思考求解动态规划

1、组成部分一:确定状态

①最后一步:

​ 在本题中,最后一步,也就是从最后一个物品出发,最后一个物品,拿还是不拿。不拿的价值是多少,拿了的价值是多少。

②子问题:

​ 前一个物品拿完之后,当前这个物品拿不拿,拿了之后价值和没拿之后的价值。

2、组成部分二:转移方程

​ 列出转移方程

3、初始条件和边界情况

  • 数组要开多大?
  • 如果重量为0,值该怎么办。
  • 如果没有物品可以拿,值该怎么办。
  • 循环要多少次

4、计算顺序

  • 由于我们是从左到右的,一直到右下角,在计算当前的时候,会参考二维数组的左面,左斜上方的格子里 的值,自然我们的计算顺序是从左到右,从上到下。
posted @ 2021-10-05 15:27  DarkerG  阅读(297)  评论(0编辑  收藏  举报