数据结构与算法Javascript描述-动态规划和贪心算法

318 阅读1分钟

动态规划和贪心算法

动态规划

动态规划被认为是一种与递归相反的技术。

递归是从顶部开始将问题分解,通过解决掉所有分解出的小问题,来解决整个问题。

动态规划解决方案是从底部开始解决问题,将所有的小问题解决掉,然后合并成一个整体解决方案,从而解决整个问题。

低效的递归

使用递归解决问题虽然简介,但效率不高。

根本原因在于指令式编程语言和面向对象对象的编程语言对递归的实现不够完善,因为它们没有将递归作为高级编程的特性。(在逐级分解递归中,导致重复计算)。

斐波那契数列

0, 1, 1, 2, 3, 5, 8, 13, ...

递归方案
function recurFib(n) {
    if (n < 3) {
        return n - 1;
    }

    // 递归分解
    return recurFib(n - 1) + recurFib(n - 2);
}
动态规划方案
function dynFib(n) {
    if (n < 3) {
        return n - 1;
    } 

    let last = 0;
    let nextLast = 1;
    let result = 0;

    // 先解决小问题
    for (let i = 2; i < n; i++) {
        result = nextLast + last;
        last = nextLast;
        nextLast = result;
    }

    return result;
}

最长公共子串

function lcs(word1, word2) {
    let max = 0;
    let index = 0;
    const lcsArr = [];

    for (let i = -1, len = word1.length; i < len; i++) { 
        lcsArr[i] = [];
    }

    // 解决小问题:存储上一级匹配长度
    for (let i = 0, len = word1.length; i < len; i++) {
        for (let j = 0, len2 = word2.length; j < len2; j++) {
            if (word2[j] === word1[i]) {
                // 整合小问题
                const count = (lcsArr[i - 1][j - 1] || 0) + 1;

                if (count > max) {
                    max = count;
                    idx = i;
                }

                lcsArr[i][j] = count;
            }
        }
    }

    if (max === 0) {
        return ''
    } else {
        return word1.slice(idx - max + 1, idx + 1);
    }
}

背包问题(0-1问题)

递归方案
const val = [4, 5, 10, 11, 13];
const size = [3, 4, 7, 8, 9];

function knapsack(capacity, size, val, n) {
    if (capacity === 0 || n === 0) {
        return 0;
    }

    if (size[n - 1] <= capacity) {
        // 分解问题:取当前物品或不取当前物品,再寻找子问题的解
        return Math.max(
            val[n - 1] + knapsack(capacity - size[n - 1], size, val, n - 1),
            knapsack(capacity - val[n - 1], size, val, n)
        );
    } else {
        return knapsack(capacity, size, val, n - 1);
    }
}
动态规划方案
const val = [4, 5, 10, 11, 13];
const size = [3, 4, 7, 8, 9];

function dknapsack(capacity, size, val, n) {
    if (capacity === 0 || n === 0) {
        return 0;
    }

    const result = [];

    for (let i = 0; i < capacity; i++) {
        result[i] = [];
    }

    // 使用二维数组保存小问题的解
    for (let i = 0; i <= n; i++) {
        for (let j = 0; j <= capacity; j++) {
            if (j === 0 || i === 0) {
                result[i][j] = 0;
            } else if (j < size[i - 1]) {
                result[i][j] = result[i - 1][j];
            } else {
                // 核心思想也是比较取当前或不取当前,和递归的区别在于,子问题已先解决
                result[i][j] = Math.max(result[i - 1][j - size[i - 1]] + val[i - 1], result[i - 1][j]);
            }
        }
    }

    return result[n][capacity];
}

贪心算法

贪心算法是一种以寻找“优质解”为手段从而达成整体解决方案的算法。

这些优质的解决方案称为局部最优解,将有希望得到正确的最终解决方案,也称为全局最优解。

“贪心”术语来自于这些算法将无论如何总是选择当前的最优解。

找零问题

const origAmt = 63;
const coins = [25, 10, 1];

function makeChange(origAmt, coins) {
    const result = [];

    for (let i = 0, len = coins.length; i < len; i++) {
        if (origAmt % coins[i]) {
            result.push(Math.floor(origAmt / coins[i]));
            origAmt = origAmt % coins[i];
        } else {
            result.push(origAmt / coins[i]);
        }
    }

    return result;
}

部分背包问题(可取部分物品)

const values = [50, 140, 60, 60];
const weights = [5, 20, 10, 12];

function ksack(values, weights, capacity) {
    let rest = capacity;
    let value = 0;

    for (let i = 0, len = values.length; i < len; i++) {
        console.log(rest, '--');
        if (rest >= weights[i]) {
            value += values[i];
            rest -= weights[i];
        } else {
            value += (values[i] * (rest / weights[i]));
            rest = 0;
        }
    }

    return value;
}

欢迎到前端自习群一起学习~516913974