动态规划 -- 钢条切割问题

给定一段长度为n英寸的钢条和一个价格表p,求切割钢条方案(钢条的长度均为整英寸),使得销售收益最大。
钢条价格表样例
我们可以计算出长度为n英寸的钢条共有2的(n-1)次方种不同的切割方案。
为解决规模为n的原问题,我们可以这样考虑。先求解形式完全一样,但规模更小的子问题。我们将钢条左边切割下长度为i的一段(i的取值范围是[1, n]),然后只对右边剩下的长度为n-i的一段继续进行切割(递归求解)。即问题的分解方式为:将长度为n的钢条分解为左边开始一段,以及剩余部分继续分解的结果。这样我们就可以做出如下解答:

function cut_rod(p, n) {
    if (n == 0) return 0;
    let sum = -Infinity;
    for (let i = 1; i <= n; i++) {
        sum = Math.max(sum, p[i] + cut_rod(p, n - i))
    }
    return sum;
}
let p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30];
console.log(cut_rod(p, 5)); //13

然而,以上代码一旦输入规模变大,程序运行时间就会变得相当长。以n=4为例,当我们对整个递归过程展开是,它所做的工作量如下:

回过头看,会发现整个过程进行了大量的重复计算,严重浪费了效率。
接下来展示如何将它转换为一个更高效的动态规划算法。动态规划方法对每个子问题只求解一次,并将结果保存下来。如果随后在此需要此子问题的解,无需重新计算,只需查找保存的结果。有两种方法:

  1. 带备忘的自顶向下法
    此方法仍然按照自然的递归形式编程,但过程中会保存每个子问题的解。
function memorized_cut_rod(p,n){
	let arr = new Array(n);
	arr.fill(-1);
	function _memorized_cut_rod(p, n, arr){
		if(arr[n] >= 0){
			return arr[n];
		}
		if(n == 0){
			return 0;
		}
		let sum = -Infinity;
		for (let i = 1; i <= n; i++) {
        	sum = Math.max(sum, p[i] + _memorized_cut_rod(p, n - i, arr))
	    }
	    arr[n] = sum;
	    return sum;
	}
	return _memorized_cut_rod(p, n, arr)
}
let p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30];
console.log(memorized_cut_rod(p, 5)); //13

对照之前的代码,动态规划方法付出了额外的内存空间,但是节省了非常多的时间。

  1. 自底向上法
    这种方法一般需要恰当定义子问题“规模”的概念,使得任何子问题的求解都只依赖于“更小的”子问题的求解。
function bottom_up_cut_rod(p, n) {
    let arr = new Array(n);
    // 长度为0时的解为0
    arr[0] = 0;
    for (let i = 1; i <= n; i++) {
        let sum = -Infinity;
        for (let j = 1; j <= i; j++) {
        	// n = 1时,i-j = 0,arr[0]已知为0
        	// n = 2时,i-j = 1、0,arr[1]、arr[0]均已知
        	// n = 3时,i-j = 2、1、0,arr[2]、arr[1]、arr[0]均已知
            sum = Math.max(sum, p[j] + arr[i - j]);
        }
        // 数组中记录子问题的解
        arr[i] = sum;
    }
    return arr[n];
}
let p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30];
console.log(bottom_up_cut_rod(p, 5)); //13

此方法采用子问题的自然顺序,若j < i,则规模为j的子问题比规模为i的子问题更小,因此,过程依次求解规模为i = 0,1,…,n的子问题。

发布了22 篇原创文章 · 获赞 7 · 访问量 9131
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章