动态规划(Dynamic programming)— JavaScript 描述

《数据结构与算法 JavaScript 描述》这本书错误好多,为什么译者不把这些错误纠正呢?

在工作中,我们都用过递归,用俗话说就是函数自己调用自己;而动态规划一般被认为是和递归相反的一种解决问题的思路:递归是从解决一个大问题开始,通过逐步解决一些小问题,来使最终的问题得到解决;动态规划的思路则恰恰相反。

我们直接来看一些例子,大家有时间可以仔细揣摩,代码有不妥的地方,多交流。

斐波拉契数列

我们先看使用递归的方式。

// 递归求斐波拉契数列 
function recurFib (n) {     
  if (2 > n) return n;     
  return recurFib(n - 1) + recurFib(n - 2); 
} 

console.group('递归') 
var time = Date.now(); 
console.log(recurFib(30)); 
console.log(Date.now() - time); 
console.groupEnd()

再看使用动态规划的思路。

// 动态规划
function dpFib (n) {
    var arr = [0, 1]
    if (n < 2) return arr[n]
    for (var i = 2; i <= n; i++) {
        arr[i] = arr[i - 1] + arr[i - 2];
    }
    return arr[n];
}

console.group('动态规划')
var time2 = Date.now();
console.log(dpFib(30));
console.log(Date.now() - time2);
console.groupEnd()

递归的代码十分简洁,但是效率很低,因为每一次都要重复进行计算,除非把一些中间结果保存下来;动态规划的代码效率很高,通过数组把一些中间值保存下来了。

顺带再看看使用循环的思路。

// 迭代
function iterFib (n) {
    if (2 > n) return n;
    var last = 1;
    var lastLast = 0;
    var now;
    for (var i = 2; i <= n; i++) {
        now = last + lastLast;
        lastLast = last;
        last = now;
    }
    return now;
}

console.group('迭代')
var time2 = Date.now();
console.log(iterFib(30));
console.log(Date.now() - time2);
console.groupEnd()

通过这几个例子,可以鲜明地感受到动态规划的思路的独特之处。再来看看其他动态规划的例子。

获取最长公共子串

比如说,两个字符串 “addcc” 和 “bddcc”,他们之间最长的相似字符是“ddcc”,怎么实现这个解法呢?

先看暴力破解的方式。

// 寻找最长公共子串,暴力解法
function longCompareStr(s1, s2) {
    var index = 0; // 索引位置
    var long = 0; // 总长度

    var tempIndex = 0; // 临时索引位置
    var tempLong = 0; // 临时长度

    for(var i = 0; i < s1.length; i++) {
        for(var j = 0; j < s2.length; j++) {
            if (s1[i] == s2[j]) { // 找到第一次相同的位置
                tempIndex = i; // 记录当前的索引位置
                tempLong++; // 长度加 1
                for(var k = 1; k < s1.length - i; k++) { // 再往下比对
                    if (s1[i+k] == s2[j+k]) {
                        tempLong++;
                    } else {
                        break;
                    }
                }

                if (tempLong > long) {
                    index = tempIndex;
                    long = tempLong;
                }
                tempLong = 0;
            }
        }
    }
    return s1.slice(index, index + long);
}

console.group('寻找最长公共子串');
console.log(longCompareStr('bccaqwerty', 'bbccqwerty2'));
console.groupEnd();

再看使用动态规划的例子。

// 使用动态规划
function dpLongCompareStr (s1, s2) {
    var index = 0; // 索引值位置
    var long = 0; // 最长相等的字符数

    var arr = [];
    for (var i = 0; i < s1.length; i++) {
        arr[i] = []
        for (var j = 0; j < s2.length; j++) {
            arr[i][j] = 0;
        }
    } // 以上代码初始化二维数组

    for (var i = 0; i < s1.length; i++) {
        for (var j = 0; j < s2.length; j++) {
            if (s1[i] == s2[j]) {
                if (i > 0 && j > 0) {
                    arr[i][j] = arr[i - 1][j - 1] + 1 // 核心代码
                } else {
                    arr[i][j] = 1
                }
            }
            
            if (arr[i][j] > long) {
                long = arr[i][j];
                index = i - long + 1; // 为什么要 +1,因为当 i = 0;long = 1;时,index = -1 不合理
            }
        }
    }
    console.log(arr)
    return s1.slice(index, index + long) || null;
}

console.group('寻找最长公共子串 --- 动态规划');
console.log(dpLongCompareStr('adfsfdfsd', 'adfsfsdfsdf'));
console.groupEnd();

啊,代码有点多,我又懒于讲解,还是仔细看代码吧。总之,使用递归可以解决的问题,也可以使用动态规划的思路去解决,而且效率更高,但是需要保持临时的计算结果。

下面是所有的代码,包含了经典的背包问题。

See the Pen 动态规划的相关例子和代码 by zeng (@zengxiaoluan) on CodePen.

LeetCode 第 70 题,也是一个动态规划的例子,可以参考学习下。

参考链接

https://zh.wikipedia.org/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92

数据结构与算法JavaScript描述

作者: 曾小乱

喜欢写点有意思的东西

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据