1. 程式人生 > >【演算法導論】動態規劃切鋼條

【演算法導論】動態規劃切鋼條

問題:鋼條切割

   給定長度為n英寸的鋼條,和一個價格表P{1....n},求切割鋼條的方案,使得收益R最大。如果鋼條價格足夠大,可以完全不用切割。

來源:演算法導論,第15章

方法:1、遞迴窮舉;2、動態規劃

思路:

遞迴窮舉:鋼條分為兩部分左邊為不切割部分範圍長度j,右邊為切割部分範圍n-j,收益Max{R = P[j]+R[n-j]}

	private static int findCutRod(int[]p,int n) {
		if(n==0){
			return 0;
		}
		int q=Integer.MIN_VALUE;
		for(int j=1;j<=n;j++){
			q =Math.max(q, p[j-1]+findCutRod(p, n-j));
		}
		
		return q;
	}

缺點:當n增加,執行時間會爆炸式增長(畫個遞迴樹,非常明白就看出來了),原因是,重複的計算了很多次

優點:不需要藉助額外的空間

動態規劃:思路與遞迴相同,

                  不同的是:建立一個數組儲存已經計算過的切割次數對應的收益,直接查表,避免重複計算

                 下面是自頂向下的程式,為了方便陣列從1開始為有效值

	private static int findCutRod_Memoized(int[] p, int n, int[] r) {//r是一個長度為n+1的陣列,原始元素小於0
		int q = Integer.MIN_VALUE;
		if (r[n] >= 0) {                         //如果已經計算過r[n],直接用
			return r[n];
		}
		if (n == 0) {
			q = 0;
		} else {
			for (int i = 1; i <= n; i++) {
				q = Math.max(q, p[i] + findCutRod_Memoized(p, n - i, r));
			}
		}
		r[n]=q;
		return q;
	}

                 這是自底向上的程式,因為用了陣列r來裝切割的結果,所以可以直接從底向上計算,

	// 為了方便,陣列下標從1開始為有效值
	// 自底向上計算,從R[1]算到R[n]
	private static int findCutRod_Memoized(int[] p, int n) {
		int[] r = new int[n + 1];//r陣列用來儲存收益
		r[0] = 0;// 0為不切割情況
		for (int i = 1; i <= n; i++) {
			int q = Integer.MIN_VALUE;
			for (int j = 1; j <= i; j++) {
				q = Math.max(q, p[j] + r[i - j]);
			}
			r[i] = q;
		}
		return r[n];
	}

優點:避免了重複計算,相對於直接遞迴減少了計算量

缺點:需要佔用額外的空間存放結果陣列