动态规划算法 动态规划有时被认为是一种与递归相反的技术。 递归是从顶部开始将问题分解,通过解决掉所有分解出小问题的方式,来解决整个问题。 动态规划解决方案从底部开始解决问题,将所有小问题解决掉,然后合并成一个整体解决方案,从而解决掉整个大问题。
使用递归去解决问题虽然简洁,但效率不高。 许多使用递归去解决的编程问题,可以重写为使用动态规划的技巧去解决。 动态规划方案通常会使用一个数组来建立一张表,用于存放被分解成众多子问题的解。 当算法执行完毕,最终的解将会在这个表中很明显的地方被找到
计算斐波拉契数列的值,使用递归算法 和 动态规范算法举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function recurFib (n ) { if (n < 2 ) return n return recurFib (n - 1 ) + recurFib (n - 2 ) } function dynFib (n ) { if (n < 2 ) return n var val = new Array (n) val[1 ] = 1 val[2 ] = 2 for (var i = 3 ; i <= n; i++) { val[i] = val[i - 1 ] + val[i - 2 ] } return val[n - 1 ] }
就上述例子而言,如果仅仅是计算出斐波拉契数列的值,其实可以不用数组 之所以使用数组就是为了保存每一步的值,然而我们其实不需要中间值,只需要最后的结果就行了。 因此,可使用迭代的方式进行优化
1 2 3 4 5 6 7 8 9 10 11 12 function iterFib (n ) { if (n < 2 ) return n var last = 1 var preLast = 1 var result = 1 for (var i = 2 ; i < n; i++) { result = preLast + last preLast = last last = result } return result }
利用动态规划找出两个字符串的最长公共子序列长度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function lcs (strX, strY ) { const m = strX.length const n = strY.length const nArr = new Array (n + 1 ).fill (0 ) const l = [] for (let i = 0 ; i < m+1 ; i++) { l[i] = [...nArr] } for (let i = 1 ; i <= m; i++) { for (let j = 1 ; j <= n; j++) { if (strX[i - 1 ] === strY[j - 1 ]) { l[i][j] = l[i - 1 ][j - 1 ] + 1 } else { l[i][j] = Math .max (l[i - 1 ][j], l[i][j - 1 ]) } } } return l[m][n] } lcs ('abbc' , 'abbbc' )
背包问题 背包问题是算法研究中的一个经典问题。 一大堆物品物品规格和价值不同,你希望用自己的小背包装这些物品,直至装满,最后结果要使背包内的物品总价值最大。
1.使用递归的方式解决背包问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function knapsack (capacity, size, value, n ) { if (n === 0 || capacity === 0 ) return 0 if (size[n - 1 ] > capacity) { return knapsack (capacity, size, value, n - 1 ) } return Math .max ( value[n - 1 ] + knapsack (capacity - size[n - 1 ], size, value, n - 1 ), knapsack (capacity, size, value, n - 1 ) ) } var value = [4 , 5 , 10 , 11 , 13 ] var size = [3 , 4 , 7 , 8 , 9 ] var capacity = 16 var n = value.length var result = knapsack (capacity, size, value, n)
2.动态规划算法 上面的递归算法能解决问题,但是性能并不好,有大量的重复计算。 使用动态规划来解决背包是一个更好的选择。 动态规划解决此问题的关键思路是计算装入背包的每一个物品的最大价值,直到背包装满。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function dKnapsack (capacity, size, value, n ) { var arr = [] for (var i = 0 ; i <= capacity + 1 ; i++) { arr[i] = [] } for (var i = 0 ; i <= n; i++) { for (var j = 0 ; j <= capacity; j++) { if (i == 0 || j == 0 ) { arr[i][j] = 0 } else if (size[i - 1 ] <= j) { arr[i][j] = Math .max ( value[i - 1 ] + arr[i - 1 ][j - size[i - 1 ]], arr[i - 1 ][j] ) } else { arr[i][j] = arr[i - 1 ][j] } } } return arr[n][capacity] } var value = [4 , 5 , 10 , 11 , 13 ] var size = [3 , 4 , 7 , 8 , 9 ] var capacity = 16 var n = value.length var result = dKnapsack (capacity, size, value, n) console .log (result)
附录 - 动态规划状态转移方程 状态转移方程,是动态规划中本阶段的状态往往是上一阶段状态和上一阶段决策的结果。 如果给定了第K阶段的状态Sk以及决策uk(Sk),则第K+1阶段的状态Sk+1也就完全确定。
也就是说得到k阶段的状态和决策后就可以得到k+1阶段的状态 状态转移就是从小规模问题的答案推导更大规模问题的答案,就是如何从已知求得未知的表达式。