JS基础--高级算法--动态规划
高级算法–动态规划
动态规则与递归的关系与区别
-
动态规划有时被认为是一种与递归相反的技术。
-
递归是从顶部开始将问题分解,通过解决掉所有分解出小问题的方式,来解决整 个问题。
-
动态规划解决方案从底部开始解决问题,将所有小问题解决掉,然后合并成一个 整体解决方案,从而解决掉整个大问题。
-
递归去解决问题虽然简洁,但效率不高,许多使用递归去解决的编程问题,可以重写为使用动态规划的技巧去解决
动态规划方案
动态规划方案通常会使用一个数组来建立一张表,用于存放被分解成众多子问题的解。当算法执行完 毕,最终的解将会在这个表中很明显的地方被找到
使用动态规划方案能做点什么?
计算斐波那契数列
斐波那契数列可以定义为以下序列:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, …
该序列是由前两项数值相加而成的
递归的实现方式:
function recurFib(n) {
if (n < 2) {
return n;
} else {
return recurFib(n-1) + recurFib(n-2);
}
}
document.write(recurFib(10)); // 显示 55
在这个计算过程当中,有太多值在递归调用中被重新计算,这也是递归方法效率低下的原因。
再看下动态规则的实现方法:
function dynFib(n) {
var val = [];
for (var i = 0; i <= n; ++i) {
val[i] = 0;
}
if (n == 1 || n == 2) {
return 1;
} else {
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];
}
}
在这个数组 val 中保存了中间结果。如果要计算的斐波那契数是 1 或者 2,那么 if 语 句会返回 1。否则,数值 1 和 2 将被保存在 val 数组中 1 和 2 的位置。循环将会从 3 到输 入的参数之间进行遍历,将数组的每个元素赋值为前两个元素之和,循环结束,数组的最 后一个元素值即为最终计算得到的斐波那契数值,这个数值也将作为函数的返回值。
斐波那契数列在数组 val 中的排列顺序如下:
val[0] = 0 val[1] = 1 val[2] = 2 val[3] = 3 val[4] = 5 val[5] = 8 val[6] = 13
寻找最长公共子串
用动态规划去寻找两个字符串的最长公共子串。例如,在单词 “raven”和“havoc”中,最长的公共子串是“av”。
原理:
使用一个二维数组存储两个字符串相同 位置的字符比较结果。初始化时,该数组的每一个元素被设置为 0。每次在这两个数组的 相同位置发现了匹配,就将数组对应行和列的元素加 1,否则保持为 0。按照这种方式,一个变量会持续记录下找到了多少个匹配项。当算法执行完毕时,这个变 量会结合一个索引变量来获得最长公共子串。
function lcs(word1, word2) {
var max = 0;
var index = 0;
var lcsarr = new Array(word1.length + 1);
for (var i = 0; i <= word1.length + 1; ++i) {
lcsarr[i] = new Array(word2.length + 1);
for (var j = 0; j <= word2.length + 1; ++j) {
lcsarr[i][j] = 0;
}
}
//上面这一部分初始化了两个变量以及一个二维数组。多数语言对二维数组的声明都很 简单,但在 JavaScript 中需要很费劲地在一个数组中定义另一个数组,这样才能声明一个 二维数组。以下代码片段中的最后一个 for 循环会对这个数组进行初始化,
for (var i = 0; i <= word1.length; ++i) {
for (var j = 0; j <= word2.length; ++j) {
if (i == 0 || j == 0) {
lcsarr[i][j] = 0;
} else {
if (word1[i - 1] == word2[j - 1]) {
lcsarr[i][j] = lcsarr[i - 1][j - 1] + 1;
} else {
lcsarr[i][j] = 0;
}
}
if (max < lcsarr[i][j]) {
max = lcsarr[i][j];
index = i;
}
}
}
//这一部分构建了用于保存字符匹配记录的表。数组的第一个元素总是被设置为 0。如果两 个字符串相应位置的字符进行了匹配,当前数组元素的值将被设置为前一次循环中数组元 素保存的值加 1。比如,如果两个字符串 "back" 和 "cace",当算法运行到第二个字符处 时,那么数值 1 将被保存到当前元素中,因为前一个元素并不匹配,0 被保存在那个元素 中(0+1)。接下来算法移动到下一个位置,由于此时两个字符仍被匹配,当前数组元素将 被设置为 2(1+1)。由于两个字符串的最后一个字符不匹配,所以最长公共子串的长度是 2。最后,如果变量 max 的值比现在存储在数组中的当前元素要小,max 的值将被赋值给这 个元素,变量 index 的值将被设置为 i 的当前值。这两个变量将在函数的最后一部分用于 确定从哪里开始获取最长公共子串。
var str = "";
if (max == 0) {
return "";
} else {
for (var i = index - max; i <= max; ++i) {
str += word2[i];
}
return str;
}
//这一部分代码用于确认从哪里开始构建这个最长公共子串。以变量 index 减去变量 max 的差值作为起始点,以变量 max 的值作为终点:
}
背包问题:递归解决方案
function max(a, b) {
return (a > b) ? a : b;
}
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);
} else {
return 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 = 5;
document.write(knapsack(capacity, size, value, n));
//结果:23
背包问题:动态规划方案
function max(a, b) {
return (a > b) ? a : b;
}
function dKnapsack(capacity, size, value, n) {
var K = [];
for (var i = 0; i <= capacity + 1; i++) {
K[i] = [];
}
for (var i = 0; i <= n; i++) {
for (var w = 0; w <= capacity; w++) {
if (i == 0 || w == 0) {
K[i][w] = 0;
} else if (size[i - 1] <= w) {
K[i][w] = max(value[i - 1] + K[i-1][w-size[i-1]], K[i-1][w]);
} else {
K[i][w] = K[i - 1][w];
}
document.write(K[i][w] + " ");
}
}
return K[n][capacity];
}
var value = [4, 5, 10, 11, 13];
var size = [3, 4, 7, 8, 9];
var capacity = 16;
var n = 5;
document.write(dKnapsack(capacity, size, value, n));
结果:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 4 4 4 4 4 4 4 4 4 4 4 4 4 4
0 0 0 4 5 5 5 9 9 9 9 9 9 9 9 9 9
0 0 0 4 5 5 5 10 10 10 14 15 15 15 19 19 19
0 0 0 4 5 5 5 10 11 11 14 15 16 16 19 21 21
0 0 0 4 5 5 5 10 11 13 14 15 17 18 19 21 23
23