動態規劃 -- 鋼條切割問題

給定一段長度爲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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章