动态规划问题同分治策略一致,都是将大问题划分为若干小问题
而动态规划问题将问题分解为不是相互独立的子问题,利用这一特点,用一个表来记录所有已经解决子问题的答案,不管该子问题以后是否被用到,只要被计算过,就将其结果填入表中
动态规划基本要素
- 最优子结构:整体问题的最优解包含子问题的最优解
- 重叠子问题:有些子问题被反复计算多次
- 备忘录方法:为了避免重复求解,建立备忘录存储已经求解过的问题结果
1.矩阵连乘问题
给定n个矩阵{A1,A2,A3,...,AN},考察这n个矩阵的乘积
A1A2A3...An
由于矩阵乘法满足结合率,故矩阵连乘有多种计算次序,这种计算次序可以用加括号的方式来确定
方法一:穷举法
列出所有可能的计算次序,并计算出每一种计算次序相应的数乘次数
方法二:动态规划
将矩阵连乘积 AiA(i+1)A(i+2)...A(j)记为A[i:j],考察其最优计算次序,从k处断开,那么计算量为
先从小问题的计算开始,每一步均选出子问题的最优解,最终得到问题的最优解
可以看到计算次序为<1,2>,<1,3>...<1,3>,<2,4>...<4,6>,<1,4>... 这样进行计算的
例子
针对上述矩阵的最优解
m[i][j]表示每个子问题的最优解,s[i][j]表示每次问题选择断点k,然后两部分计算如下
void MatrixChain(int *p,int n,int **m,int **s){
for (int i = 1; i <= n; i++) m[i][i] = 0;//自己到自己代价为0
for (int r = 2; r <= n; r++)//每个斜线的循环,共画出n-1条(去掉了对角线)
for (int i = 1; i <= n –(r-1); i++)//每次的乘法计算循环,最开始的时候为n-1次然后次数越来越少
{
int j=i+r-1;
m[i][j] = m[i+1][j]+ p[i-1]*p[i]*p[j];
s[i][j] = i; //记录最少乘法的位置
for (int k = i+1; k < j; k++) {
int t = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
if (t < m[i][j]) { m[i][j] = t; s[i][j] = k;}
}
}
输出最优计算次序
public static void TraceBack (int [][]s,int i,int j)
{
if (i==j) return;
TraceBack(s, i, s[i][j]);
TarceBack(s,s[i][j]+1,j);
System.out.println(“Multiply A”+i+”,”+s[i][j]+”and A”+(s[i][j]+1)+”,”+j);
}
2.最长公共子序列
若给定序列 X = {X1,X2,...,Xm} 则Z为X的子序列是指存在一个严格递增的下标序列使得对于所有的j = 1,2,3...,k 均存在Zj = Xij
例子:序列Z={B,C,D,B}是序列X={A,B,C,B, D,A,B}的子序列,相应的递增下标序列为{2,3,5, 7}。
给定2个序列X和Y,当另一序列Z既是X的子序列又是 Y的子序列时,称Z是序列X和Y的公共子序列。
目标:找出X和Y的最长公共子序列
设序列X={x1 ,x2 ,…,xm}和Y={y1 ,y2 ,…,yn }的最长公共子序列为 Z={z1 ,z2 ,…,zk }
- 若xm=yn,则zk=xm=yn,且Zk-1是Xm-1和Yn-1的最长公共子序列。
- 若xm≠yn且zk≠xm,则Z是Xm-1和Y的最长公共子序列。
- 若xm≠yn且zk≠yn,则Z是X和Yn-1的最长公共子序列。
n,m,k是序列长度,而减去1表示去掉最后一个元素
对于上述性质1,在直到最后一个公共元素是zk的时候,去掉三个序列中的该元素,那么就可以知道最长公共子序列
对于性质2,如果xm不是最后一个公共子元素,去掉无影响,性质三同理
得到如下递归公式
用c[i][j]记录序列Xi和Yj的最长公共子序列的长度。
第三个递推代表着如果xi不等于yj的时候,看看哪一个序列减去之后最长子序列长度会不变,当减到xi = yj的时候,长度加一,index均减一,继续递归
void LCSLength(int m,int n,char *x,char *y,int **c,int **b)
{
int i,j;
for (i = 1; i <= m; i++) c[i][0] = 0;
for (i = 1; i <= n; i++) c[0][i] = 0;//当两个子序列其中一个长度为0的时候,最长子序列长度为0
for (i = 1; i <= m; i++)
for (j = 1; j <= n; j++) {
if (x[i]==y[j]) {
c[i][j]=c[i-1][j-1]+1; b[i][j]=1;}//使用b[i][j]来标记三种情况
else if (c[i-1][j]>=c[i][j-1]) {
c[i][j]=c[i-1][j]; b[i][j]=2;}
else { c[i][j]=c[i][j-1]; b[i][j]=3; }
}
}
note:这里解释一下三种个情况,分别为最后一位相等,X序列的最后一位是公共子序列,Y序列的最后一位是公共子序列
void LCS(int i,int j,char *x,int **b)
{
if (i ==0 || j==0) return;
if (b[i][j]== 1){ LCS(i-1,j-1,x,b); cout<<x[i]; }//情况一:两者相等
else if (b[i][j]== 2) LCS(i-1,j,x,b);//情况二:Y序列最后