01背包,从简单递归到递归记忆化搜索到动态规划

原题链接

左神(左程云)视频讲解

递归版

递归考虑思路,从第一块物品开始我们根据当前背包剩余的容量有两种选择

第一种是选择当前物品加入背包

第二种是不选择当前物品加入背包

1.当我们的容量不足以装下当前物品时,我们只有第一种选择,即抛弃当前物品,继续判断下一个物品

if (restV - vi[index] < 0) {
            return digui(restV, index + 1);
}
//restV表示剩余容量,index表示当前正在处理第几个物品

2.当我们的容量足以当下当前物品时,我们就有两种选择了,即选择当前物品或抛弃当前物品,并返回两种选择的最大值

return Math.max(digui(restV - vi[index], index + 1) + wi[index],
                digui(restV, index + 1));

3.那么我们的递归要如何结束呢,显然当我们选完所有物品后递归结束

if (index == vi.length) {
            return 0;
}

4.最后的完整代码

注意 N物品数量

V最大容积

vi[]物品重量数组

wi[]物品价值数组

以上都是全局变量

import java.util.Scanner;
public class Main {
    public static int N, V;
    public static int vi[];
    public static int wi[];
    public static Scanner sc = new Scanner(System.in);
    // 返回的值为价值
    public static int digui(int restV, int index) {
        //递归出口,选完所有物品
        if (index == vi.length) {
            return 0;
        }
        // 如果当前的物品的容量大于剩余背包的容量,则不可能加入
        if (restV - vi[index] < 0) {
            return digui(restV, index + 1);
        }
        // 返回加入与不加入的最大值
        return Math.max(digui(restV - vi[index], index + 1) + wi[index],
                digui(restV, index + 1));
    } 
    public static void main(String[] args) {
        int N = sc.nextInt();
        int V = sc.nextInt();
        vi = new int[N];
        wi = new int[N];
        for (int i = 0; i < N; i++) {
            vi[i] = sc.nextInt();
            wi[i] = sc.nextInt();
        }
        System.out.println(digui(V, 0));
    }
}

记忆化搜索版

让我们考虑观察该递归函数

int digui(int restV, int index);

我们发现该函数返回一个整数,参数分别是restV(剩余容量)和index(当前正在处理的物品)

如果我们将函数看成一种映射,也就是说当restV和index固定的时候,返回值也就随之固定了

也就是说,一旦restV和index固定,函数的结果就实际上已经确定了

于是我们可以用一个二位数组dp[][]来记录参数是restV和index时函数返回的结果

于是我们可以将递归函数改成下面的样子

        dp[][] = {-1} //dp初始化为-1 
public static int memory(int restV, int index) {
        if (dp[restV][index] != -1) {
            return dp[restV][index];
        }
        if (index == vi.length) {
            dp[restV][index] = 0;
            return dp[restV][index];
        }
        // 如果当前的物品的价值大于整个背包的容量,则不可能加入
        if (restV - vi[index] < 0) {
            dp[restV][index] = digui(restV, index + 1);
            return dp[restV][index];
        }
        // 返回加入与不加入的最大值
        dp[restV][index] = Math.max(digui(restV - vi[index], index + 1) + wi[index],
                digui(restV, index + 1));
        return dp[restV][index];
    }

动态规划版

让我们观察记忆化版

我们发现当index == vi.length时其实函数的返回结果就已经确定了是0

于是我们可以直接赋值dp[restV][vi.length] = 0;

这就是dp数组的初态

index

restV

0

1

2

.....

vi.length-1

vi.length

0

-1

-1

-1

-1

-1

0

1

-1

-1

-1

-1

-1

0

2

-1

-1

-1

-1

-1

0

...

-1

-1

-1

-1

-1

0

V-1

-1

-1

-1

-1

-1

0

V

-1

-1

-1

-1

-1

0

从右往左看,并观察函数

if (restV - vi[index] < 0) {
        dp[restV][index] = digui(restV, index + 1);
        return dp[restV][index];
}
// 返回加入与不加入的最大值
dp[restV][index] = Math.max(digui(restV - vi[index], index + 1) + wi[index],
                            digui(restV, index + 1));
return dp[restV][index];

我们发现vi.length - 1的值是由vi.length的值得来的

如果能装下第vi.length-1的物品那么返回值就是max(dp[vi.length][restV],0) = dp[vi.length][restV]

所以我们就得到了下面的dp状态

index 如果剩余的容量能装下wi[length],就填wi

restV

0

1

2

.....

vi.length-1

vi.length

0

-1

-1

-1

-1

0

0

1

-1

-1

-1

-1

0

0

2

-1

-1

-1

-1

0

0

...

-1

-1

-1

-1

...

0

V-1

-1

-1

-1

-1

wi[length]

0

V

-1

-1

-1

-1

wi[length]

0

然后表继续从右往左填,直到填完所有的表

最后返回dp[V][0]的值即可

完整代码如下

import java.util.Scanner;

public class Main {
    public static int N, V;
    public static int vi[];
    public static int wi[];
    public static Scanner sc = new Scanner(System.in);
    public static int dp[][];

    // 返回的值为价值
    public static int digui(int restV, int index) {
        if (index == vi.length) {
            return 0;
        }
        // 如果当前的物品的价值大于整个背包的容量,则不可能加入
        if (restV - vi[index] < 0) {
            return digui(restV, index + 1);
        }
        // 返回加入与不加入的最大值
        return Math.max(digui(restV - vi[index], index + 1) + wi[index],
                digui(restV, index + 1));
    }

    // 返回的值为价值
    public static int memory(int restV, int index) {
        if (dp[restV][index] != -1) {
            return dp[restV][index];
        }
        if (index == vi.length) {
            dp[restV][index] = 0;
            return dp[restV][index];
        }
        // 如果当前的物品的价值大于整个背包的容量,则不可能加入
        if (restV - vi[index] < 0) {
            dp[restV][index] = digui(restV, index + 1);
            return dp[restV][index];
        }
        // 返回加入与不加入的最大值
        dp[restV][index] = Math.max(digui(restV - vi[index], index + 1) + wi[index],
                digui(restV, index + 1));
        return dp[restV][index];
    }

    public static void main(String[] args) {
        int N = sc.nextInt();
        int V = sc.nextInt();
        vi = new int[N];
        wi = new int[N];
        dp = new int[V + 1][N + 1];
        for (int restV = 0; restV < V + 1; restV++) {
            for (int index = 0; index < N + 1; index++) {
                dp[restV][index] = -1;
            }
        }
        for (int i = 0; i < N; i++) {
            vi[i] = sc.nextInt();
            wi[i] = sc.nextInt();
        }
        //System.out.println(digui(V, 0));
       // System.out.println(memory(V, 0));
        for (int restV = 0; restV < V + 1; restV++) {
            for (int index = 0; index < N + 1; index++) {
                dp[restV][index] = -1;
            }
        }
        for (int restV = 0; restV < V; restV++) {
            dp[restV][vi.length] = 0;
        }
        for (int index = N - 1; index >= 0; index--) {
            for (int restV = 0; restV <= V; restV++) {
                if (restV - vi[index] < 0) {
                    dp[restV][index] = dp[restV][index + 1];

                } else {
                    dp[restV][index] = Math.max(dp[restV - vi[index]][index + 1] + wi[index],
                            dp[restV][index + 1]);
                }

            }
        }

        System.out.println(dp[V][0]);

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值