对其余动态规划问题感兴趣的,也能够查看算法
详解动态规划最少硬币找零问题--JavaScript实现编程
一开始在接触动态规划的时候,可能会云里雾里,彷佛能理解思路,可是又没法准确地表述或者把代码写出来。本篇将一步一步经过做图的方式帮助初次接触动态规划的同窗来理解问题。这一篇将以经典的 01背包 问题为例子来说解,最后经过纯 JavaScript 来实现,在 Sublime 上运行演示。固然若是不会 JavaScript 也一点关系都没有,由于最重要的是理解整个推导过程。在语言实现的时候,也没有涉及什么语言特性,基本上懂个C语言就能看懂了。bash
给定一个固定大小的背包,背包的容量为 capacity,有一组物品,存在对应的价值和重量,要求找出一个最佳的解决方案,使得装入背包的物品总重量不超过背包容量 capacity,并且总价值最大。本题中给出了3个物品,其价值和重量分别是 (3,2),(4,3),(5,4)。括号左边为价值,右边为重量,背包容量 capacity 为5。那么求出其搭配组合,使得背包内总价最大,且最大价值为多少?编程语言
在开始计算以前,须要先对动态规划中的01背包问题有基本的理解:post
清楚了上面的原则以后,就能够开始进行分析了。 当一个新物品出现的时候,须要去决策若是选择了它,是否会让总价值最大化。咱们根据问题,创建以下表格用于分析: ui
咱们对这个表格作一下说明,左上角 val
和 w
分别是物品的价值和重量。即上面所描述的3个物品的价值与重量对应关系。spa
从第三列到最后一列,使用了变量 j,它表示背包总容量,最大值为5,也就是前面问题所说的 capacity 的值。3d
第二行到最后一行,使用 i 表示,下标从0开始,一共有3个物品,因此 i 的最大值为 2。即咱们使用i表示物品,在下面介绍中将i=0
称为物品0
,i=1
称为物品1
,以此类推。code
除了 j = 0 的状况之外,咱们将从左到右,从上到下一步一步去填写这个表格,来找到最大的价值。
表格中未填写的空格,表示背包内物品总价值。咱们后面将使用 T[i][j]
二维数组来表示它。
若是背包总容量为0,那么很显然地,任何物品都没法装进背包,那么背包内总价值必然是0。因此第一步先填满 j=0 的状况。
正如上面所说,咱们接下来将从上到下,从左往右地填写这个表格。因此如今把注意力定位到 i =0, j = 1 的空格上。
在分析过程当中,有一个重要原则:分析第i行时,它的物品组合仅能是小于等于i的状况。
怎么理解这个原则:好比分析i=0这一行,那么背包里只能装入物品0,不能装入其余物品。分析i=1这一行,物品组合能够是物品0和物品1
i=0 j=1
: 背包总容量为1,可是物品0 的重量为 2,没法装下去,因此这一格应该填 0。
i=0 j=2
: 背包总容量为2,恰好能够装下物品0 ,因为物品0 的价值为3,所以这一格填 3。
i=0 j=3
: 背包总容量为3,因为根据上面说明的物品组合原则,第0行,仅能放物品0,不须要考虑物品1 和 物品2,因此这一格填 3。
i=0 j=4
: 同理,填 3 。
i=0 j=5
: 同理,填 3 。
这样咱们能够完成第0行的填写,以下图:
在这一行,能够由物品0 和物品1 进行自由组合,来装入背包。 i=1 j=1
: 背包总容量为1,可是物品0 的重量为 2,物品1重量为3,背包没法装下任何物品,因此填 0。
i=1 j=2
: 背包总容量为2,只能装下物品0,因此填 3。
i=1 j=3
: 背包总容量为3,这时候能够装下一个物品1,或者一个物品0,仅仅从人工填表的方式,很容易理解要选择物品1,可是咱们该如何以一个确切的逻辑来表达,让计算机明白呢?基于上面说说明的价值和重量在表格中从上到下递增原则,能够确认物品1的价值是大于物品0的,因此默认状况下优先考虑物品1,当选择了物品1以后,把背包剩余的容量和物品1以前的物品重量对比(也就是和物品0的重量对比,若是剩余重量能装下前面的物品,那么就继续装)。因此这里选择物品1,填 4
i=1 j=4
: 选择了物品1以后,物品1 的重量为3,背包容量为4, 减去物品1的重量后, 剩余容量为1,没法装下物品0,因此这里填 4
i=1 j=5
选择了物品1以后,剩余的容量为2,恰好能够装下物品0,因此一格背包装了物品1,和物品 0,总价值为7,把 7 填入表格。
这样咱们就完成了第二行的填写,以下图:
i=2 j=1
: 填 0 。
i=2 j=2
: 填写这一行时,3种物品都有机会被装入背包。总容量为2时,只能装物品0,因此填 3。
i=2 j=3
: 物品2的重量为4,大于容量j,因此这里能够参考 T[i-1][j]的值,也就是 i=1 j=3
那一格的值,填 4。
i=2 j=4
: 能够装下物品2,价值为5。也能够装下物品1。这一空格须要谨慎一点。咱们将使用更严谨的方式来分析。在i=1 j=5
中出现了物品组合一块儿装入背包的状况,这一空将延续这种分析方式。咱们选择了物品2,剩余的容量表达式应为 j-w[i] 即 4 - 4 = 0,剩余的容量用于上一行的搜索,因为上一行咱们是填写完的,因此能够很轻易地获得这个值。表达式能够写成 val[i] + T[i][j-w[i]]
,能够根据这个表达式得出一个值。可是这并非最终结果,还须要和上一行同一列数值对比,即 T[i-1][j]
,对比,取最大值。最后这里填 5。
i=2 j=5
: 根据上面计算原理,这里若是选择了物品2,那么最大价值只能5,参照上一行,同一列,价值为7,取最大值。因此放弃物品2,选择将物品0和物品1装入背包,填写7。
完成后的表格以下:
理解了上面整个填表过程,咱们要把逻辑抽取出来,在具体代码实现以前,先用伪代码表达出来。
if(j < w[i]){ //容量小于重量,hold不住
T[i][j] = T[i-1][j]; //因此值等于上一行,同一列。若是i=0,没有上一行,则T[i][j] 取0
}else{
T[i][j] = max(val[i] + T[i-1][j-w[i]] , T[i-1][j]); //参照上面 i=2 j=4 和 i=2 j=5 时的填表分析
}
复制代码
以上这简短的伪代码就是解决问题的核心思路,能够应用于任何你熟悉的编程语言上。
说到这里,这篇文章应该是要基本告一段落了。
若是你已经理解了上面的填表分析和伪代码表达,那么就能够尝试着本身去用代码实现了。最后放出在使用 JavaScript的一种实现方式供你们参考,再也不针对针对代码作太多说明,重要区域会有注释。若是 Sublime 支持纯 JavaScript,能够直接复制黏贴,command+b 运行看结果。
function knapSack(w,val,capacity,n){
var T = []
for(let i = 0;i < n;i++){
T[i] = [];
for(let j=0;j <= capacity;j++){
if(j === 0){ //容量为0
T[i][j] = 0;
continue;
}
if(j < w[i]){ //容量小于物品重量,本行hold不住
if(i === 0){
T[i][j] = 0; // i = 0时,不存在i-1,因此T[i][j]取0
}else{
T[i][j] = T[i-1][j]; //容量小于物品重量,参照上一行
}
continue;
}
if(i === 0){
T[i][j] = val[i]; //第0行,不存在 i-1, 最多只能放这一行的那一个物品
}else{
T[i][j] = Math.max(val[i] + T[i-1][j-w[i]],T[i-1][j]);
}
}
}
findValue(w,val,capacity,n,T);
return T;
}

//找到须要的物品
function findValue(w,val,capacity,n,T){
var i = n-1, j = capacity;
while ( i > 0 && j > 0 ){
if(T[i][j] != T[i-1][j]){
console.log('选择物品'+i+',重量:'+ w[i] +',价值:' + values[i]);
j = j- w[i];
i--;
}else{
i--; //若是相等,那么就到 i-1 行
}
}
if(i == 0 ){
if(T[i][j] != 0){ //那么第一行的物品也能够取
console.log('选择物品'+i+',重量:'+ w[i] +',价值:' + values[i]);
}
}
}
// w = [2,3,4]. val = [3,4,5] , n = 3 , capacity = 5
//function knapSack([2,3,4],[3,4,5],5,3);
//
var values = [3,4,5],
weights = [2,3,4],
capacity = 5,
n = values.length;
console.log(knapSack(weights,values,capacity,n));
复制代码
输出结果