【算法】【动态规划】钢条切割

1  题目

来自算法导论的一道经典题目:

2  解答

动态规划原理
虽然已经用动态规划方法解决了上面问题,但是大家可能还跟我一样并不知道什么时候要用到动态规划。总结一下上面的斐波拉契数列和钢条切割问题,发现两个问题都涉及到了重叠子问题,和最优子结构。

①最优子结构

用动态规划求解最优化问题的第一步就是刻画最优解的结构,如果一个问题的解结构包含其子问题的最优解,就称此问题具有最优子结构性质。因此,某个问题是否适合应用动态规划算法,它是否具有最优子结构性质是一个很好的线索。使用动态规划算法时,用子问题的最优解来构造原问题的最优解。因此必须考查最优解中用到的所有子问题。

②重叠子问题

在斐波拉契数列和钢条切割结构图中,可以看到大量的重叠子问题,比如说在求fib(6)的时候,fib(2)被调用了5次,在求cut(4)的时候cut(0)被调用了4次。如果使用递归算法的时候会反复的求解相同的子问题,不停的调用函数,而不是生成新的子问题。如果递归算法反复求解相同的子问题,就称为具有重叠子问题(overlapping subproblems)性质。在动态规划算法中使用数组来保存子问题的解,这样子问题多次求解的时候可以直接查表不用调用函数递归。

其实按我的理解:

(1)大问题能不能拆成小问题(没法拆成小问题,那还用什么动态规划。。。),小问题的解能不能推导出大问题的解(如果小问题推到不出大问题的解,那拆的还有什么意义呢?是不是)

(2)重复小问题(递归比如斐波那契,里边就有很多重复的递归操作比如 5 要求3和4,4 里边又要递归3 和2,计算3 重复了吧,很多的重复小问题,可以用备忘录也就是数组或者map缓存中间结果,或者自底向上的计算哈)

那么这个问题的解:我们用递归和动态规划来解一下:

2.1  递归

/**
 * 计算长度为 n 的钢条的最大收益
 * @param map 长度收益映射表
 * @param n 钢条长度
 * @return 最大收益值
 */
public static int maxIncome(int[] map, int n) {
    // 钢筋长度边界考虑
    int res = 0;
    if (n <= 0) {
        return res;
    }
    // 递归的话
    // n 的最大收益值 = 切割 n 次的最大收益(当 n = n 时,就是不切割的情况)
    for (int i = 1; i <= n; i++) {
        res = Math.max(res, map[i-1] + maxIncome(map, n - i));
    }
    return res;
}
public static void main(String[] args) {
    int[] map = new int[]{1, 5 ,8, 9, 10, 17, 17, 20, 24, 30};
    System.out.println(maxIncome(map, 4));
}

2.2  备忘录递归

/**
 * 计算长度为 n 的钢条的最大收益
 * @param map 长度收益映射表
 * @param n 钢条长度
 * @return 最大收益值
 */
public static int maxIncome(int[] map, int[] cache, int n) {
    // 钢筋长度边界考虑
    int res = 0;
    if (n <= 0) {
        return res;
    }
    // 先从缓存中拿,因为钢筋收益肯定是大于0的,所以这里大于0就表示有计算过
    if (cache[n] > 0) {
        System.out.println("命中缓存:" + n);
        return cache[n];
    }
    // 递归的话
    // n 的最大收益值 = 切割 n 次的最大收益(当 n = n 时,就是不切割的情况)
    for (int i = 1; i <= n; i++) {
        res = Math.max(res, map[i-1] + maxIncome(map, cache, n - i));
    }
    cache[n] = res;
    return res;
}
public static void main(String[] args) {
    // 长度收益
    int[] map = new int[]{1, 5, 8, 9, 10, 17, 17, 20, 24, 30};
    // 钢筋长度
    int n = 4;
    // 缓存
    int[] cache = new int[n + 1];
    System.out.println(maxIncome(map, cache, n));
    System.out.println(Arrays.toString(cache));
}

4里边会计算2,3里边也会计算2,所以2命中过一次

2.3  动态规划-自底向上

/**
 * 计算长度为 n 的钢条的最大收益
 * @param map 长度收益映射表
 * @param n 钢条长度
 * @return 最大收益值
 */
public static int maxIncome(int[] map, int n) {
    // 缓存
    int[] cache = new int[n + 1];
    // 自底向上 直接从1开始计算到n
    for (int i = 1; i <= n; i++) {
        int segRes = 0;
        for (int j = 1; j <= i; j++) {
            segRes = Math.max(segRes, map[j - 1] + cache[i - j]);
        }
        cache[i] = segRes;
    }
    return cache[n];
}
public static void main(String[] args) {
    // 长度收益
    int[] map = new int[]{1, 5, 8, 9, 10, 17, 17, 20, 24, 30};
    // 钢筋长度
    int n = 4;
    System.out.println(maxIncome(map, n));
}

3  小结

子问题推断大问题的公式这个要理解,好久没看动态规划了= =,适应中....也感谢铁子的讲解:https://blog.csdn.net/u013309870/article/details/75193592 讲的挺好。

posted @ 2024-02-14 13:11  酷酷-  阅读(115)  评论(0编辑  收藏  举报