15.1-1 由公式(15.3)和初始條件T(0) = 1,證明公式(15.4)成立。
ANSWER:
15.1-2 舉反例證明下面的“貪心”策略不能保證總是得到最優切割方案。定義長度為i的鋼條的密度為Pi / i,即每英寸的價值。貪心策略將長度為n的鋼條切割下長度為i (1 ≤ i ≤ n)的一段,其密度最高。接下來繼續使用相同的策略切割長度為n-i的剩余部分。
ANSWER:當長度n = 4時,按照“貪心”策略則切割成長度為1和3的鋼條(p = 1 + 8 = 9);而最優解為切割成2條長度為2的鋼條(p = 5 + 5 = 10 > 9)。
15.1-3 我們對鋼條切割問題進行一點修改,除了切割下的鋼條段具有不同價值Pi外,每次切割還要付出固定的成本c。這樣,切割方案的收益就等於鋼條段的價格之和減去切割成本。設計一個動態規划算法解決修改后的鋼條切割問題。
ANSWER:多新建一個數組m[0...n]記錄每個長度的鋼條最優解的切割段數,當完成長度為i的鋼條最優解時,更新長度為i+1時使m[i+1] = m[j] + 1,其中長度為i+1的鋼條切割成長度為(i+1-j)和j的兩大段,長度為j的鋼條繼續切割。
- let r[0...n] and m[0...n] be new arrays
- r[0] = 0, m[0] = 0
- for i = 1 to n
- q = -∞
- for j = 1 to i
- if q < p[j] + r[i-j] - m[i-j]*c
- q = p[j] + r[i-j] - m[i-j]*c
- m[i] = m[i-j] + 1
- r[i] = q
- return r[n]
//15.1-3帶有固定切割成本的鋼條切割方案 #if 0 #include <iostream> using namespace std; int Max(int a,int b) { return a>b?a:b; } struct array { int r;//代表最大收益 int s;//代表切割方案 }; struct array *EXTENDED_BOTTOM_UP_CUT_ROD(int p[],int n) { struct array *rs=new struct array[n]; rs[0].r=0; int q; for (int j=0;j<n;j++) { int flag=1;//哨兵為1代表無切割。 q=p[j]; for (int i=0;i<j;i++) { //q=Max(q,p[i]+rs[j-i].r-1); if(q<=p[i]+rs[j-i].r-1)//切割固定成本c=1 { q=p[i]+rs[j-i].r-1; rs[j+1].s=i+1; flag=0;//哨兵為0代表有切割。 } } if (j==i)//i=j代表無切割 { if (q<=p[i]+rs[j-i].r&&flag) {//無切割時注意切割方案就等於鋼條長度。 rs[j+1].s=i+1; } } rs[j+1].r=q; } return rs+n; } void PRINT_CUT_ROD_SOLUTION(int p[],int n) { struct array *rs=EXTENDED_BOTTOM_UP_CUT_ROD(p,n); while (n>0) { cout<<(*rs).s<<" "; n=n-(*rs).s; rs=rs-(*rs).s; } } void main() { const int n=10; int p[10]={1,5,8,9,10,17,17,20,24,30}; cout<<(*EXTENDED_BOTTOM_UP_CUT_ROD(p,10)).r<<endl; PRINT_CUT_ROD_SOLUTION(p,10); }
15.1-4 修改MEMOIZED-CUT-ROD,使之不進返回最優收一只,還返回切割方案。
ANSWER:利用數組s的結果可以得到切割方案,新建存儲長度為n的鋼條切割方案數組t[0...n]
CUT_WAY(s, n):
- i = 0
- while n > 0
- t[i] = s[n]
- n = n - s[n]
- i = i + 1
- return t[0...i-1]
1 //15.1-4動態規划法遞歸求解最優鋼條切割問題。不僅返回收益還返回切割方案。 2 #include <iostream> 3 using namespace std; 4 struct array 5 { 6 int r;//代表最大收益 7 int s;//代表切割方案 8 }; 9 int Max(int a,int b) 10 { 11 return a>b?a:b; 12 } 13 struct array *MEMOIZED_CUT_ROD_AUX(int p[],int n,struct array rs[]) 14 { 15 static int q; 16 if (rs[n].r>=0) 17 { 18 return rs+n; 19 } 20 if (n==0) 21 { 22 q=0; 23 } 24 else 25 { 26 q=-0x7fffffff; 27 for (int i=0;i<n;i++) 28 { 29 int t=p[i]+MEMOIZED_CUT_ROD_AUX(p,n-i-1,rs)->r; 30 if (q<t) 31 { 32 q=t; 33 rs[n].s=i+1;//n相當於上面迭代里的j+1,i+1和上面迭代一樣。 34 } 35 } 36 } 37 rs[n].r=q; 38 return rs+n; 39 } 40 struct array *MEMOIZED_CUT_ROD(int p[],int n) 41 { 42 struct array *rs=new struct array [n]; 43 for (int i=0;i<=n;i++) 44 { 45 rs[i].r=-0x7fffffff; 46 } 47 return MEMOIZED_CUT_ROD_AUX(p,n,rs); 48 } 49 void PRINT_CUT_ROD_SOLUTION(int p[],int n) 50 { 51 struct array *rs=MEMOIZED_CUT_ROD(p,n); 52 cout<<"最大收益:"<<rs->r<<endl; 53 cout<<"切割方案:"; 54 while (n>0) 55 { 56 cout<<(*rs).s<<" "; 57 n=n-(*rs).s; 58 rs=rs-(*rs).s; 59 } 60 } 61 void main() 62 { 63 const int n=10; 64 int p[10]={1,5,8,9,10,17,17,20,24,30}; 65 PRINT_CUT_ROD_SOLUTION(p,10); 66 cout<<endl; 67 }
15.1-5 斐波那契數列可以用遞歸式(3.22)定義。設計一個O(n)時間的動態規划算法計算第n個斐波那契數。畫出字問題圖。圖中有多少頂點和邊?
ANSWER:O(n)時間,O(1)空間
- On_for_Fibonacci_sequence(n):
- if n <= 2
- return 1
- x = 1, y = 1
- for i = 3 to n
- res = x + y
- x = y
- y = res
- return res
子問題圖:
圖中有n個頂點,(2n-3)條邊。
15.2-1 對矩陣規模序列{5,10,3,12,5,50,6},求矩陣鏈最優括號化方案。
利用上面代碼:得到下圖所示結果:

15.2-2設計遞歸算法MATRIX-CHAIN-MULTIPLY(A,s,i,j),實現矩陣鏈最優代價乘法計算的真正計算過程,其輸入參數為矩陣序列{A1,A2,...,An},MATRIX-CHAIN-ORDER得到的表s,以及下標i和j.(初始調用應為MATRIX-CHAIN-MULTIPLY(A,s,1,n)).
1 #include <iostream> 2 using namespace std; 3 #define n 5//可以更改矩陣的數量 4 struct Matrix 5 { 6 int rows;//表示行數。 7 int columns;//表示列數。 8 int **M; 9 Matrix(int i=0,int j=0) 10 { 11 rows=i; 12 columns=j; 13 M=new int *[rows]; 14 for (int k=0;k<rows;k++) 15 { 16 M[k]=new int [columns]; 17 } 18 }; 19 }; 20 struct Matrix_CHAIN//矩陣鏈 21 { 22 int **m;//運算次數 23 int **s;//划分方式 24 Matrix_CHAIN() 25 { 26 m=new int *[n-1]; 27 for (int k=0;k<n-1;k++) 28 { 29 m[k]=new int [n-1]; 30 } 31 s=new int *[n-2]; 32 for (int t=0;t<n-1;t++) 33 { 34 s[t]=new int [n-1]; 35 } 36 } 37 }; 38 struct Matrix init(struct Matrix A) 39 { 40 for (int i=0;i<A.rows;i++) 41 { 42 for (int j=0;j<A.columns;j++) 43 { 44 A.M[i][j]=rand()%10; 45 } 46 } 47 return A; 48 } 49 struct Matrix Matrix_MULTIPLY(struct Matrix A,struct Matrix B) 50 { 51 if (A.columns!=B.rows) 52 { 53 struct Matrix D(1,1); 54 D.M[0][0]=0; 55 cerr<<"incompatible dimensions"<<endl; 56 return D; 57 } 58 else 59 { 60 struct Matrix C(A.rows,B.columns); 61 for (int i=0;i<A.rows;i++) 62 { 63 for (int j=0;j<B.columns;j++) 64 { 65 C.M[i][j]=0; 66 for (int k=0;k<A.columns;k++) 67 { 68 C.M[i][j]=C.M[i][j]+A.M[i][k]*B.M[k][j]; 69 } 70 } 71 } 72 return C; 73 } 74 } 75 struct Matrix_CHAIN MATRIX_CHAIN_ORDER(int p[]) 76 { 77 int N=n-1; 78 Matrix_CHAIN T; 79 for (int i=0;i<N;i++) 80 { 81 T.m[i][i]=0; 82 } 83 for (int l=2;l<=N;l++) 84 { 85 for (int i=1;i<=N-l+1;i++) 86 { 87 int j=i+l-1; 88 T.m[i-1][j-1]=0x7fffffff; 89 for (int k=i;k<=j-1;k++) 90 { 91 int q=T.m[i-1][k-1]+T.m[k][j-1]+p[i-1]*p[k]*p[j]; 92 if (q<T.m[i-1][j-1]) 93 { 94 T.m[i-1][j-1]=q; 95 T.s[i-1][j-1]=k-1; 96 } 97 } 98 } 99 } 100 return T; 101 } 102 //15.2-2矩陣鏈總乘法 103 struct Matrix MATRIX_CHAIN_MULTIPLY(Matrix A[],Matrix_CHAIN T,int i,int j) 104 { 105 if (j==i) 106 { 107 return A[i]; 108 } 109 if (j==i+1) 110 { 111 return Matrix_MULTIPLY(A[i],A[j]); 112 } 113 Matrix t1=MATRIX_CHAIN_MULTIPLY(A,T,i,T.s[i][j]); 114 Matrix t2=MATRIX_CHAIN_MULTIPLY(A,T,T.s[i][j]+1,j); 115 return Matrix_MULTIPLY(t1,t2); 116 } 117 void PRINT_OPTIMAL_PARENS(Matrix_CHAIN T,int i,int j) 118 { 119 if (i==j) 120 { 121 cout<<"A"<<i; 122 } 123 else 124 { 125 cout<<"("; 126 PRINT_OPTIMAL_PARENS(T,i,T.s[i][j]); 127 PRINT_OPTIMAL_PARENS(T,T.s[i][j]+1,j); 128 cout<<")"; 129 } 130 } 131 void Print(struct Matrix A) 132 { 133 for (int i=0;i<A.rows;i++) 134 { 135 for (int j=0;j<A.columns;j++) 136 { 137 cout<<A.M[i][j]<<" "; 138 } 139 cout<<endl; 140 } 141 } 142 void main() 143 { 144 //int p[n]={5,10,3,12,5,50,6}; 145 //int p[n]={30,35,15,5,10,20,25}; 146 int p[n]={2,3,4,5,6}; 147 struct Matrix_CHAIN T=MATRIX_CHAIN_ORDER(p); 148 for (int i=0;i<n-1;i++) 149 { 150 for (int j=0;j<n-1;j++) 151 { 152 if (T.m[i][j]<0) 153 { 154 cout<<"-1"<<"\t"; 155 } 156 else cout<<T.m[i][j]<<"\t"; 157 } 158 cout<<endl; 159 } 160 PRINT_OPTIMAL_PARENS(T,0,n-2); 161 struct Matrix A[n]={0}; 162 for (int j=1;j<n;j++) 163 { 164 struct Matrix t(p[j-1],p[j]); 165 A[j-1]=t; 166 init(A[j-1]); 167 cout<<endl; 168 Print(A[j-1]); 169 cout<<endl; 170 } 171 struct Matrix C=MATRIX_CHAIN_MULTIPLY(A,T,0,n-2); 172 Print(C); 173 }
由於數組下標是從0開始的,所以 這里調用應為MATRIX-CHAIN-MULTIPLY(A,s,0,n-2)).n為矩陣鏈元素個數,上面代碼中的n為矩陣鏈長度,實際矩陣個數N=n-1個。
15.2-3 用代入法證明遞歸公式(15.6)的結果為Ω(2^n).
P(n-1)=P(1)P(n-2)+P(2)P(n-3)+....+P(n-2)P(1) P(n)=P(1)P(n-1)+P(2)P(n-2)+....+P(n-1)P(1) 因為P(1)<P(2)<...P(n-1) 所以P(n)>P(1)P(n-1)+P(n-1) 因為P(1)=1 P(n)>2P(n-1)
猜想P(n)>c2^n,則P(n-1)>c2^(n-1) 所以P(n)>2c2^(n-1)=c2^n 得證!
15.2-4 對輸入鏈長度為n的矩陣連乘法問題,描述其子問題圖:它包含多少個頂點?包含多少條邊?這些分別連接哪些頂點?
包含n²個頂點,n³條邊連接着n²個頂點。
15.2-5 令R(i,j)表示在一次調用MATRIX-CHAIN-ORDER過程中,計算其他表項時訪問表項m[i,j]的次數。證明:
(提示:證明中可用到公式A.3)

15.2-6 證明:對n個元素的表達式進行完全括號化,恰好需要n-1對括號。
利用歸納法即可證明。當n≤2時 恰好有1對括號。假設當n=k(2<k<n)時,有k-1對括號。則n=k+1時,現在我們把這k+1個矩陣分成k個矩陣+1個矩陣,前k個矩陣在前面假設中已知只要加k-1對括號就可以使最終結果是最優的,那么保持前k個矩陣的括號划分,並且得到最優結果矩陣后,再與第k+1個矩陣相乘,這時問題又轉化為2個矩陣相乘的簡單問題了,由前面已知需要1對括號,那么總的括號就是k-1+1=k對括號,在n=k+1時,也成立。所以利用歸納法得證!
15.3動態規划原理
15.3-1對於矩陣鏈乘法問題,下面兩種確定最優代價的方法哪種更高效?第一種方法是窮舉所有可能的括號化方案,對每種方案計算乘法運算次數,第二種方法是運行RECURSIVE-MATRIX-CHAIN。證明你的結論。
書中對枚舉法已經給出其運行時間,就是類似卡塔蘭數的序列,運算次數為Ω(4^n/n^(3/2))。而用朴素遞歸方式求全部解的運算次數為O(n3^n).顯然用遞歸方式求解更高效。
15.3-2 對一個16個元素的數組,畫出2,.3-1節中MERGE-SORT過程運行的遞歸調用樹。解釋備忘技術為什么對MERGE-SORT這種分治算法無效。
參考書中圖2-4,這里只是把原書8個元素擴展到16個元素,兩者類似。由2-4圖可以看出,每個子問題都是全新的,不存在重疊子問題,所以圖中問題適合分治而不適合動態規划。
15.3-3 考慮矩陣鏈乘法問題的一個變形,目標改為最大化矩陣序列括號花方案的變量乘法運算次數,而非最小化。此問題具有最優子結構性質嗎?
由於矩陣鏈中的子問題鏈為AiAi+1...Ak和Ak+1Ak+2....Aj的乘法問題,子鏈是互不相交的,因此任何矩陣都不會同時包含在兩個子鏈中。這樣我們說矩陣鏈子問題是無關的。於是我們可以用“剪切-黏貼”技術來加以證明這個事具有最優子結構的。
15.3-3思路供參考:確定一個問題是否具有最優子結構要考慮兩個方面的問題:1、子問題是否是獨立。2、子問題是否是重疊
先分析第一個問題:最大矩陣鏈乘法子問題是將矩陣鏈分為兩部分,求前一部和后一部分最大值,然后合並,而這兩個子問題的 最大值是在兩個矩陣鏈中分別求解,所以這兩個子問題沒有重復(在這里最鮮明的對照就是無權圖中最長簡單路徑問題,該問題如果分為兩個子問題,前一部分的最長路徑可能包含后一部分的最長路徑,兩個子問題合並之后的路徑就不是簡單路徑了)。再看子問題是否重疊,如果子問題中的子問題是一個跟子問題不一樣的問題則為不重疊,如果將子問題的矩陣鏈分解為兩個孫矩陣鏈,同樣是求兩個孫矩陣鏈各自的最大值然后合並,該孫子問題與子問題相同,即為重疊,所以最大矩陣鏈乘法具有最優子結構。
代碼如下:
#include<iostream> using namespace std; //p為矩陣鏈,p[0],p[1]代表第一個矩陣,p[1],p[2]代表第二個矩陣,length為p的長度 //所以如果有六個矩陣,length=7,m為存儲最優結果的二維矩陣,t為存儲選擇最優結果路線的 //二維矩陣 void MatrixChainOrder(int *p,int (*m)[10],int (*t)[10],int length) { int n=length-1; int i,j,k,q,num=0; //A[i][i]只有一個矩陣,所以相乘次數為0,即m[i][i]=0; for(i=1;i<length;i++) { m[i][i]=0; } //i代表矩陣鏈的長度,i=2表示有兩個矩陣相乘時如何划分 for(i=2;i<=n;i++) { //j表示從第j個矩陣開始的i個矩陣如何划分是最優 for(j=1;j<=n-i+1;j++) { //k為從第j個數i個矩陣就是k,從j到k表示他們之間的i個矩陣如何划分 k=j+i-1; //m[j][k]存儲了從j到k使用最佳划分所得到的最優結果 m[j][k]=0; //q為介於j到k-1之間的數,目的是利用q對j到k之間的矩陣進行試探性的划分, //從而找到最優划分,這是一種遍歷性的試探。 for(q=j;q<=k-1;q++) { num=m[j][q]+m[q+1][k]+p[j-1]*p[q]*p[k]; if(num>m[j][k]) { m[j][k]=num; t[j][k]=q; } } } } } void PrintAnswer(int(*t)[10],int i,int j) { if(i==j) { cout<<"A"<<i; } else { cout<<"("; PrintAnswer(t,i,t[i][j]); PrintAnswer(t,t[i][j]+1,j); cout<<")"; } } int main() { int p[7]={30,35,15,5,10,20,25}; int m[10][10],t[10][10]; MatrixChainOrder(p,m,t,7); // MemorizedMatrixChain(p,m,t,7); PrintAnswer(t,1,6); cout<<endl; cout<<m[1][6]<<endl; return 0; }
15.3-4 <1,1,2,3>
貪心:[1,3]=[1,1]+[2,3]+1*1*3=1*2*3+3=9
正確結果:[1,3]=[1,2]+[3,3]+1*2*3=1*1*2+6=8
15.3-5 對15.1節的鋼條切割問題加入限制條件:假定對於每種鋼條長度i(i=1,2,...n-1),最多允許切割出li段長度為i的鋼條。證明:15.1節所描述的最優子結構性質不再成立。
從圖中看出如果切割1個長度為4的鋼條,最佳切割方式是切成4條長度為1的鋼條可以獲得60最大收益,但是我們給每種長度對應的條數做了限制,比如長度為1的鋼條最多只能切割2段,那么4>2超出預期。所以我們在不違反切割限制前提下只能選擇切成長度分別為1,1和2的鋼條作為最佳切割方式,收益為50。由於加了切割限制,所以最優子結構性質不再成立。
最優子結構:問題的最優解由相關子問題的最優解組合而成,而這些子問題可以獨立求解。
因為此時每種長度的切割數被限制,也就是說此時使用長度i時,會影響子問題的li 大小,是相關的(可以把每種長度的限制加入狀態中,來消除相關性。但維數太多無法實現)。即建立[rn ,L],L為li 集合,但是子問題的狀態數很大,幾乎等於窮舉了
15.3-6
Dm =max{(Di -ci )ri,j }=max{Diri,j -ci *ri,j },兩個子問題相關,具有同樣變量ri,j
15.4.1

15.4-2
//LCS #include<iostream> #include<string> using namespace std; //改進的LCS算法,不使用數組b便可打印出結果 void LCS_LengthC(string x,string y,int (*c)[100]) { int m,n; m=x.length(); n=x.length(); int i,j; //如果i或j等於0則c[i][j]=0; for(i=1;i<=m;i++) { c[i][0]=0; } for(i=1;i<=n;i++) { c[0][i]=0; } //遍歷兩個字符串,依次標記c[i][j],c[i][j]標記了從x的開始到第i個元素與從 //y開始到第j個元素中LCS的長度 //數組b用來標記最長公共子串所要走的路線,該路線為兩個字符串組成的矩陣中的對應的字母 for(i=1;i<=m;i++) { for(j=1;j<=n;j++) { if(x[i-1]==y[j-1]) { c[i][j]=c[i-1][j-1]+1; } else { if(c[i][j-1]>c[i-1][j]) { c[i][j]=c[i][j-1]; } else { c[i][j]=c[i-1][j]; } } } } } void PrintAnswerC(string x,string y,int(*c)[100],int i,int j) { if(i==0||j==0) { return ; } else { if(x[i-1]==y[j-1]) { PrintAnswerC(x,y,c,i-1,j-1); cout<<x[i-1]<<" "; } else if(c[i-1][j]>=c[i][j-1]) { PrintAnswerC(x,y,c,i-1,j); } else { PrintAnswerC(x,y,c,i,j-1); } } } int main() { string x="abcbda"; string y="bdcaba"; int c[100][100]={0}; int b[100][100]={0}; LCS_LengthC(x,y,c); cout<<"the LCS is: "<<c[x.length()][y.length()]<<endl; PrintAnswerC(x,y,c,x.length(),y.length()); return 0; }
15.4-3
#include <stdio.h> #include <stdlib.h> #include <string.h> int lcsLength(int **c,char *x,char *y,int i,int j) { if(i<0 || j<0) return -1; if(c[i][j] != -1) return c[i][j]; if(x[i-1]==y[j-1]) { int a=lcsLength(c,x,y,i-1,j-1); c[i][j]=a+1; } else { int up=lcsLength(c,x,y,i-1,j); int left=lcsLength(c,x,y,i,j-1); if(up>=left) c[i][j]=up; else c[i][j]=left; } return c[i][j]; } int** initC(char *x,char *y) { int xlen=strlen(x); int ylen=strlen(y); int **c=(int**)malloc((xlen+1)*sizeof(int*)); for(int i=0;i<xlen+1;i++) { c[i]=(int*)malloc((ylen+1)*sizeof(int)); } for(int i=0;i<xlen+1;i++) for(int j=0;j<ylen+1;j++) c[i][j]=-1; for(int i=0;i<xlen+1;i++) c[i][0]=0; for(int j=0;j<ylen+1;j++) c[0][j]=0; return c; } void printLCS(int **c,char *x,char *y) { int xlen=strlen(x); int ylen=strlen(y); int i=xlen,j=ylen; char *s=(char*)malloc((xlen)*sizeof(char)); while(i>=1 && j>=1) { if(x[i-1]==y[j-1]) { s[i-1]=x[i-1]; i--; j--; } else if(c[i][j]==c[i-1][j]) { s[i-1]='*'; i--; } else j--; } for(;i<xlen;i++) { if(s[i]!='*') printf("%c ",s[i]); } printf("\n"); } void printC(int **c,int xlen,int ylen) { for(int i=1;i<xlen+1;i++) { for(int j=1;j<ylen+1;j++) { if(c[i][j]==-1) printf("%d ",c[i][j]); else printf("%d ",c[i][j]); } printf("\n"); } } void main() { char *x="ABCBDAB"; char *y="BDCABA"; int **c=initC(x,y); lcsLength(c,x,y,strlen(x),strlen(y)); printLCS(c,x,y); //printC(c,strlen(x),strlen(y)); getchar(); }
