高效算法之动态规划(第15章)
有人說:越炫耀什么,越缺少什么。但我卻以為:越缺少什么,越覺得別人炫耀什么。 ——李宮俊《李宮俊的詩》
0. 前言
參考圖書《算法導論》
動態規劃通常用來解決最優化問題,在這類問題中,我們通常做出一組選擇來表達最優解。在做出這個選擇的同時,通常會生成與原問題形式相同的子問題。當多于一個選擇子集都生成相同的子問題時,動態規劃技術通常很有效,其關鍵技術就是對每一個這樣的子問題都保存其解,當其重復出現的時候即可避免重復求解。這種思想可以將指數時間的算法轉換為多項式時間的算法。
動態規劃(dynamic programming)是運籌學的一個分支,是求解決策過程(decision process)最優化的數學方法。動態規劃(dynamic programming)中的programming指的是一種表格法,不是編寫計算機程序。
1. 動態規劃解析
采用動態規劃求解的問題的一般要具有3個性質:
(1) 最優化原理:如果問題的最優解所包含的子問題的解也是最優的,就稱該問題具有最優子結構,即滿足最優化原理。 (2) 無后效性:即某階段狀態一旦確定,就不受這個狀態以后決策的影響。也就是說,某狀態以后的過程不會影響以前的狀態,只與當前狀態有關。 (3)有重疊子問題:即子問題之間是不獨立的,一個子問題在下一階段決策中可能被多次使用到。(該性質并不是動態規劃適用的必要條件,但是如果沒有這條性質,動態規劃算法同其他算法相比就不具備優勢)。動態規劃求解基本步驟:
1. 刻畫一個最優解的結構特征。2. 遞歸的定義最優解的值。3. 計算最優解的值,通常采用自底向上的方法。4. 利用計算出的信息構造一個最優解。2. 動態規劃的應用
2.1 鋼條切割
問題陳述:給定一個長度為n英寸的鋼條和一個價格表pi(i=1,2,3,4....,n),求切割鋼條的方案,使得銷售收入最大。
| 價格pi | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
問題分析:從題目中我們可以得出給定的長度為n的鋼條有2n?1種不同的切割方案,因為在距離鋼條左端i(i=1,2,3..,n?1)英寸處,我們總是可以選擇切割或者不切割。對于上述的價格表樣例,我們可以觀察所有最優收益值ri(i=1,2,3,4...10) 以及對應的最優切割方案:
r1=1, 切割方案1=1(無切割)
r2=5, 切割方案2=2(無切割)
r3=8, 切割方案3=3(無切割)
r4=10, 切割方案4=2+2
r5=13, 切割方案5=2+3
r6=17, 切割方案6=6(無切割)
r7=18, 切割方案7=6+1或者7=2+2+3
r8=22, 切割方案8=2+6
r9=25, 切割方案9=3+6
r10=30, 切割方案10=10(無切割)
更一般的對于rn(n>=1), 我們可以用更短的鋼條的最優切割收益來描述它:
注意,為了求解規模為n的原問題,我們求解形式完全一樣(最優解結構特征刻畫),完成首次切割之后我們將兩段鋼條看作兩個獨立的鋼條切割問題,通過組合兩個相關子問題的最優解(最優子結構),并且在所有可能的兩段切割方案中選取組合收益最大者,構成原問題的最優解。
除了上述求解方式還有一種遞歸求解的方式公式如下:
rn=max(pi+rn?1)(1≤i≤n)
算法設計:
自頂向下的普通遞歸算法設計:
使用動態規劃的算法設計:
//算法1:帶備忘錄的自頂向下法 MemorizedSteelCut(p[],n){int r[] = new int[n];for(i=0 to n){r[i]=-1;}return MemorizedSteelCutAux(p[],n,r); } MemorizedSteelCutAux(p[],n,r){if(r[n]>=0){return r[n];}if(n==0){q=0;}else{q=-1;for(i=1 to n){q=max(q,p[i]+MemorizedSteelCutAux(p[],n-i,r)); }}r[n]=q;return q; }//算法2:自底向上法 BottomUpSteelCut(p[],n){int r[] = new int[n];r[0]=0;for(j=1 to n){q=-1;for(i=1 to j){q=max(q,p[i]+r[j-i]);}r[j]=q;} }Java實現:
package lbz.ch15.dp.ins1; /** * @author LbZhang* @version 創建時間:2016年3月4日 下午2:20:33 * @description 鋼條切割問題*/ public class SteelCutting {public static void main(String[] args) {System.out.println("DP 在鋼條切割問題中的應用 ");int price[] = {1,5,8,9,10,17,17,20,24,30};int n = 10;//自頂向下的遞歸實現int result = 0;result = TopToBottomRecursion(price,n);System.out.println("常規的思路:"+result);//使用動態規劃來實現 備忘錄result=MemorizedSteelCut(price,n);System.out.println("備忘錄法:"+result);//使用動態規劃的自底向上非遞歸的實現result=BottomToTopSteelCut(price,n);System.out.println("自底向上非遞歸方法:"+result);}private static int BottomToTopSteelCut(int[] price, int n) {int r[] = new int[n+1];r[0]=0;//動態表的開頭for(int j=1;j<=n;j++){int q=-1;for(int i=1;i<=j;i++){q=maxOfTwo(q,price[i-1]+r[j-i]);}r[j]=q;}return r[n];}/*** 備忘錄方法* @param price* @param n* @return*/private static int MemorizedSteelCut(int[] price, int n) {int r[] = new int[n+1];for(int i=0;i<=n;i++){r[i]=-1;}return MemorizedSteelCutAux(price,n,r);}/*** 輔助過程的備忘錄核心算法* @param price* @param n* @param r* @return*/private static int MemorizedSteelCutAux(int[] price, int n, int[] r) {if(r[n]>=0){return r[n];}int q=0;if(n==0){q=0;}else{q=-1;for(int i=1;i<=n;i++){//price[i-1] 應為price的下標是從0開始,q=maxOfTwo(q,price[i-1]+MemorizedSteelCutAux(price,n-i,r)); }}r[n]=q;return q;}/*** //自頂向下的遞歸實現 常規思路* @param price ``````````````````````````````````````````````````````````````````* @param n* @return*/private static int TopToBottomRecursion(int[] price, int n) {if(n==0) return 0;int q = -1;if(n<=price.length){q=price[n-1];}for(int i=1;i<n;i++){q=maxOfTwo(q,price[i-1]+TopToBottomRecursion(price,n-i));}return q;}private static int maxOfTwo(int x, int y) {return x>y?x:y;//三目運算符的使用}}重構解-對源程序進行修改
private static int BottomToTopSteelCut(int[] price, int n) {int r[] = new int[n+1];int s[] = new int[n+1];r[0]=0;//動態表的開頭for(int j=1;j<=n;j++){int q=-1;for(int i=1;i<=j;i++){//q=maxOfTwo(q,price[i-1]+r[j-i]);if(q<price[i-1]+r[j-i]){q=price[i-1]+r[j-i];s[j]=i;}}r[j]=q;}System.out.println();for(int temp=0;temp<=n;temp++){System.out.print(s[temp]+"|-"+temp+"-|");}System.out.println();//正確的組合輸出printToFormal(s);return r[n];}private static void printToFormal(int[] s) {int len=s.length-1;int temp=s[len];System.out.print("鋼條切割的組合方式: "+temp+" ");while(temp!=len){len=len-temp;temp=s[len];System.out.print("+ "+temp+" ");}System.out.println();}2.2 斐波那契數列
下面直接給出斐波那契數列的Java實現的O(n)的動態規劃算法實現。
package lbz.ch15.dp.ins1;/*** @author LbZhang* @version 創建時間:2016年3月7日 下午9:42:15* @description 類說明*/ public class MemoryAndTable {static int MAX = 20;static int[] lookUp = new int[MAX];public static int fibMemory(int n) {if (lookUp[n] == 0) {if (n <= 1) {lookUp[n] = n;} else {lookUp[n] = fibMemory(n - 1) + fibMemory(n - 2);}}return lookUp[n];}// //打表(自下而上)public static int fibTable(int n) {int[] f = new int[n + 1];int i;f[0] = 0;f[1] = 1;for (i = 2; i <= n; i++) {f[i] = f[i - 1] + f[i - 2];}return f[n];}public static void main(String[] args) {int n = 5;System.out.println(fibMemory(9));System.out.println();// int res=0;// res=fibTable(9);System.out.println(fibTable(9));} }注意:在動態規劃中,子問題解決方案被存儲在一個表中,以便這些不必重新計算。 因此,如果這個問題是沒有共同的(重疊)子問題, 動態規劃是沒有用的。例如,二分查找不具有共同的子問題。
轉載于:https://www.cnblogs.com/mrzhang123/p/5365807.html
總結
以上是生活随笔為你收集整理的高效算法之动态规划(第15章)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【WS-Federation】到底有多少
- 下一篇: egret中loadingUI的自定义