动态规划算法-背包问题

68 阅读4分钟

动态规划常被人比作是递归的逆过程,而贪心算法在很多求优问题上(也许不是最优解),是不二之选

动态规划

动态规划

  1. 从底部开始解决问题,将所有小问题解决掉,然后合并成一个整体的解决方案。
  2. 使用一个数组建立一张表,用于存放被分解成众多子问题的解。 递归:是从顶部开始将问题分解,通过解决掉所有分解出小问题的方式,来解决整个问题。

使用递归去解决问题虽然简洁,但效率不高。包括 JavaScript 在内的众多语言,不能高效地将递归代码解释为机器代码,尽管写出来的程序简洁,但是执行效率低下。但这并不是说使用递归是件坏事,本质上说,只是那些指令式编程语言和面向对象的编程语言对递归 的实现不够完善,因为它们没有将递归作为高级编程的特性。

斐波拉契数列

递归解决:

function fibo (n) {
    if (n <= 0)  return 0;
    if (n === 1) return 1;
    return fibo(n - 1) + fibo(n - 2);
}

动态规划解决

使用动态规划解决最简单的子问题着手的话,效果就很不一样了。存取每一次产生子问题的结果a,b

function fibo (n) {
    if (n <= 0) return 0;
    if (n <= 1) return 1;
    var a = 0, b = 1;
    for (var i = 2; i <= n; i++) {
        b = a + b;
        a = b - a;
    }
    return b;
}

寻找最长公共子串

比如 abcdefg 和 emefabcde

暴力循环解决

function maxSubString (str1, str2) {
    if (!str1 || !str2) return '';
    var len1 = str1.length,
        len2 = str2.length;
    var maxSubStr = '';
    for (var i = 0; i < len1; i++) {
        for (var j = 0; j < len2; j++) {
            var tempStr = '',
                k = 0;
            while ((i + k < len1) && (j + k < len2) && (str1[i + k] === str2[j + k])) {
                tempStr += str1[i + k];
                k++;
            }
            if (tempStr.length >  maxSubStr.length) {
                maxSubStr = tempStr;
            }
        }
    }
    return maxSubStr;
}

动态规划

function maxSubString(str1, str2){
    var maxLen = 0;
    var index = 0;

    var arr = new Array();
    for (var i = 0; i <= str1.length + 1; i++) {
        arr[i] = new Array();
        for (var j = 0; j <= str2.length + 1; j++) {
            arr[i][j] = 0;
        }
    }

    for(var i = 0; i <= str1.length; i++){
        for(var j = 0; j <= str2.length; j++){
            if(i == 0 || j == 0){
                arr[i][j] = 0
            }else{
                if (str1[i] == str2[j] && str1[i - 1] == str2[j - 1]) {
                    arr[i][j] = arr[i - 1][j - 1] + 1;
                }else{
                    arr[i][j] = 0;
                }
            }
            if(arr[i][j] > maxLen){
                maxLen = arr[i][j];
                index = i;
            }
        }
    }

    var str = "";
    if(maxLen == 0){
        return "";
    }else{
        return str1.slice(index,maxLen);
    }
}
maxSubString(str1, str2)     // abcde

背包问题

递归解决

function knapsack (capacity, objectArr, order) {
    if (order < 0 || capacity <= 0) {
        return 0;
    }
    if (arr[order].size > capacity) {
        return knapsack(capacity, objectArr, order - 1);
    }
    return Math.max(
    arr[order].value + knapsack(capacity - arr[order].size, objectArr, order - 1),
    knapsack(capacity, objectArr, order - 1)
    );
}

console.log(knapsack(16, [
    {value: 4, size: 3},
    {value: 5, size: 4},
    {value: 10, size: 7},
    {value: 11, size: 8},
    {value: 13, size: 9}
], 4)); // 23

动态规划

为了提高程序的运行效率,我们不妨将递归实现方式改成动态规划。这个问题有个专业的术语:0-1背包问题。0-1背包问题,dp解法历来都困扰很多初学者,大多人学一次忘一次,那么,这次我们努力💪将它记在心里。

注意,理解0-1背包问题的突破口,就是要理解 “0-1” 这个含义,这里对于每一件物品,要么带走(1),要么留下(0)。

基本思路

0-1背包问题子结构:选择一个给定第 i 件物品,则需要比较选择第 i 件物品的形成的子问题的最优解与不选择第 i 件物品的子问题的最优解。分成两个子问题,进行选择比较,选择最优的。

若将 f[i][w] 表示前 i 件物品恰放入一个容量为 w 的背包可以获得的最大价值。则其状态转移方程便是:

f[i][w] = max{ f[i-1][w], f[i-1][w-w[i]]+v[i] }

其中,w[i] 表示第 i 件物品的重量,v[i] 表示第 i 件物品的价值。

function knapsack (capacity, objectArr) {
    var n = objectArr.length;
    var f = [];
    for (var i = 0; i <= n; i++) {
        f[i] = [];
        for (var w = 0; w <= capacity; w++) {
            if (i === 0 || w === 0) {
                f[i][w] = 0;
            } else if (objectArr[i - 1].size <= w) {
                var size = objectArr[i - 1].size,
                    value = objectArr[i - 1].value
                f[i][w] = Math.max(f[i - 1][w - size] + value, f[i - 1][w]);
            } else {
                f[i][w] = f[i - 1][w];
            }
        }
    }
    return f[n][capacity];
}

以上方法空间复杂度和时间复杂都是O(nm),其中 n 为物品个数,m 为背包容量。时间复杂度没有优化的余地了,但是空间复杂我们可以优化到O(m)。首先我们要改写状态转移方程:

f[w] = max{ f[w], f[w-w[i]]+v[i] }
function knapsack (capacity, objectArr) {
    var n = objectArr.length;
    var f = [];
    for (var w = 0; w <= capacity; w++) {
        for (var i = 0; i < n; i++) {
            if (w === 0) {
                f[w] = 0;
            } else if (objectArr[i].size <= w) {
                var size = objectArr[i].size,
                    value = objectArr[i].value
                f[w] = Math.max(f[w - size] + value, f[w] || 0);
            } else {
                f[w] = Math.max(f[w] || 0, f[w - 1]);
            }
        }
    }
    return f[capacity];
}

参考链接:https://juejin.cn/post/6844903895748067341

参考链接:https://github.com/Checkson/blog/issues/34