文档章节

【动态规划】01背包问题【续】

o
 osc_a22drz29
发布于 2019/03/24 11:29
字数 1662
阅读 14
收藏 0

精选30+云产品,助力企业轻松上云!>>>

说明

这段时间每天加班,确实没有整块的时间来写博客了,一不小心就到周末了,要是不写篇博客,那就又要鸽了。为了不打脸,还是加班加点的把这篇博客给写了出来。

20190323214213.png

再说个题外话,最近一直在看一本关于Mysql的掘金小册,感觉很棒,作者用通俗易懂的语言将Mysql的底层原理进行了介绍,图文并茂,讲解的很深入,可以看出作者应该是花了不少心思,借阅了不少书籍的。据说作者是个95后,为了写这本小册子还特意辞了职,简直优秀!

20190323215229.png

一篇文章大概需要花费40~60分钟,建议花整块的时间进行阅读。

从作者的身上,也看到了一种匠心精神,反观自己,写这么水的文章,实在是惭愧。所以决定对文章质量把把关,本着宁缺毋滥的原则来写作,尽量不浪费大家的时间。

好了,闲话就说到这了,言归正传。

上一篇中,我们了解了01背包问题,并用三种方法进行了求解,但其实在最后一种解法上,我们还能再对它的空间复杂度进行优化。

优化过程

已经过去一个星期了,可能一部分人已经忘记了之前的解题思路,所以在这里把之前填表法使用到的图贴了过来:

这是我们上一篇填表法的最终结果,在这里,聪明的你应该能发现,其实这里大部分的内容都没有用上,那么让我们来想想,如何优化一下空间复杂度呢?

再回头看下之前的递推关系式:

可以发现,每次求解 KS(i,j)只与KS(i-1,m) {m:1...j} 有关。也就是说,如果我们知道了K(i-1,1...j)就肯定能求出KS(i,j),为了更直观的理解,再画一张图:

下一层只需要根据上一层的结果即可推出答案,举个栗子,看i=3,j=5时,在求这个子问题的最优解时,根据上述推导公式,KS(3,5) = max{KS(2,5),KS(2,0) + 3} = max{6,3} = 6;如果我们得到了i=2时所有子问题的解,那么就很容易求出i=3时所有子问题的解。

因此,我们可以将求解空间进行优化,将二维数组压缩成一维数组,此时,装填转移方程变为:

KS(j) = max{KS(j),KS(j - wi) + vi}

这里KS(j - wi)就相当于原来的KS(i-1, j - wi)。需要注意的是,由于KS(j)是由它前面的KS(m){m:1..j}推导出来的,所以在第二轮循环扫描的时候应该由后往前进行计算,因为如果由前往后推导的话,前一次循环保存下来的值可能会被修改,从而造成错误。

这么说也许还是不太清楚,回头看上面的图,我们从i=2推算i=3的子问题的解时,此时一维数组中存放的是{0,0,2,4,4,6,6,6,6,6,6},这是i=2时所有子问题的解,如果我们从前往后推算i=3时的解,比如,我们计算KS(0) = 0,KS(1) = KS(1) = 0 (因为j=1时,装不下第三个珠宝,第三个珠宝的重量为5),KS(2) = 2,KS(3) = 4,KS(4) = 4, KS(5) = max{KS(5), KS(5-5) + 3} = 6,....,KS(8) = max{KS(8),KS(8 - 5) + 3} = 7。在这里计算KS(8)的时候,我们就把原来KS(8)的内容修改掉了,这样,我们后续计算就无法找到这个位置的原值(这个栗子没举好。。因为后面的计算没有用到KS(8)= =),也就是上一轮循环中计算出来的值了,所以在遍历的时候,需要从后往前进行倒序遍历。

public class Solution{
    int[] vs = {0,2,4,3,7};
    int[] ws = {0,2,3,5,5};
    int[] newResults = new int[11];

    @Test
    public void test() {
        int result = ksp(4,10);
        System.out.println(result);
    }

    private int ksp(int i, int c){
        // 开始填表
        for (int m = 0; m < vs.length; m++){
            int w = ws[m];
            int v = vs[m];
            for (int n = c; n >= w; n--){
                newResults[n] = Math.max(newResults[n] , newResults[n - w] + v);
            }
            // 可以在这里输出中间结果
            System.out.println(JSON.toJSONString(newResults));
        }
        return newResults[newResults.length - 1];
    }
}

输出如下:

[0,0,0,0,0,0,0,0,0,0,0]
[0,0,2,2,2,2,2,2,2,2,2]
[0,0,2,4,4,6,6,6,6,6,6]
[0,0,2,4,4,6,6,6,7,7,9]
[0,0,2,4,4,7,7,9,11,11,13]
13

这样,我们就顺利将空间复杂度从O(n*c)优化到了O(c)。当然,空间优化的代价是,我们只能知道最终的结果,但无法再回溯中间的选择,也就是无法根据最终结果来找到我们要选的物品组合。

关于初始值

01背包问题一般有两种不同的问法,一种是“恰好装满背包”的最优解,要求背包必须装满,那么在初始化的时候,除了KS(0)0,其他的KS(j)都应该设置为负无穷大,这样就可以保证最终得到的KS(c)是恰好装满背包的最优解。另一种问法不要求装满,而是只希望最终得到的价值尽可能大,那么初始化的时候,应该将KS(0...c)全部设置为0

为什么呢?因为初始化的数组,实际上是在没有任何物品可以放入背包的情况下的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可以在什么都不装且价值为0的情况下被“恰好装满”,其他容量的背包均没有合法的解,因此属于未定义的状态,应该设置为负无穷大。如果背包不需要被装满,那么任何容量的背包都有合法解,那就是“什么都不装”。这个解的价值为0,所以初始状态的值都是0。

总结

01背包问题可以用自上而下递归记忆法求解,也可以用自下而上填表法求解,而后者可以将二维数组的解空间优化成一维数组的解空间,从而实现空间复杂度的优化。

对于01背包问题的两种不同问法,实际上的区别便是初始值设置不一样,解题思路是一样的。

关于01背包问题,介绍到这里就已经全部结束了,希望能对大家有所帮助。如果觉得有收获,不要吝啬你的赞哦,也欢迎关注我的公众号留言交流。

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
【动态规划】一次搞定三种背包问题

前文链接 【动态规划】01背包问题 【动态规划】01背包问题【续】 【动态规划】完全背包问题 【动态规划】多重背包问题 说明 看完前面四篇关于背包问题的文章,你会发现背包问题其实也不过如此...

osc_es532h90
2019/05/11
3
0
算法3:背包问题

背包问题和01背包问题是很经典的关于动态规划和贪心算法的题目。 这两个问题很相似,01背包是有一个容量为c的背包,装入一些质量为w[ ]的且价值为v[ ]的物品,每次只能选择放入或者不放,不能...

osc_22to9se2
2019/08/27
1
0
动态规划理解

看了知乎用户徐凯强 Andy与王勐回答动态规划提问感觉受益匪浅。在这写下自己理解。 首先要明确,动态规划的本质是找到合适的状态的定义与找到状态转移方程。递归与递推只是实现动态规划的工具...

osc_avwazwuz
2018/10/18
2
0
知其所以然(续)

这篇同样转自http://mindhacks.cn上刘未鹏的blog。 查了一下,上篇知其所以然(以学习算法为例)是08年7月写的,现在已经是10年11月,过去了两年零4个月,这说明了三件事情:1,一个问题其实...

adgkns
2012/12/31
28
0
动态规划——背包问题

  背包问题是一类经典的动态规划问题,本节只介绍两类最简单的背包问题:01 背包问题和完全背包问题。 一、多阶段动态规划问题   有一类动态规划可解的问题,它可以描述成若干个有序的阶...

osc_k4rs0tfs
2018/02/10
1
0

没有更多内容

加载失败,请刷新页面

加载更多

Linux系统检查用户账户到期时间

如果你在 Linux 上启用了密码策略。密码必须在到期前进行更改,并且登录到系统时会收到通知。如果你很少使用自己的帐户,那么可能由于密码过期而被锁定。在许多情况下,这可能会在无需密码登...

老孟的Linux私房菜
15分钟前
9
0
关于南京哪里有开餐饮费发票?

关于南京哪里有开餐饮费发票?聚焦餐饮行业,谈话〖18 7一電一7 5 3 8一徴一3331〗研究院昨发布数据显示,今年上半年,全国餐饮行业招聘需求增长46.18%,平均月薪6387元.随着餐饮行业的快速...

点击fojewio
48分钟前
7
0
android studio 4.0 打开DDMS

1、先找到AndroidStudio配置的SDK路径; 2、在SDK的/tools/路径下有个monitor.bat 的批处理文件; 3、鼠标连续点击两下monitor.bat这个批处理文件,在屏幕上会打开一个类似CMD的命令行中输入...

chenhongjiang
50分钟前
10
0
如何在Android中使用SharedPreferences来存储,获取和编辑值

问题: Closed . 已关闭 。 This question needs to be more focused. 这个问题需要更加集中。 It is not currently accepting answers. 它当前不接受答案。 Learn more . 了解更多 。 Want...

fyin1314
今天
6
0
【JDK1.8】LinkedList源码分析

LinkedList的特性 LinkedList内部使用双向链表作为存储结构,LinkedList可以理解为链表的扩展对象,封装了常用的和非常用的操作链表的方法。以及在通过索引获取元素时的简单优化,通常Linke...

XuePeng77
今天
36
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部