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(); }