算法篇-3-动态规划-矩阵连乘&最长公共子序列&最大字段和

本系列所有代码https://github.com/YIWANFENG/Algorithm-github

动态规划

其实动态规划和递归分治有异曲同工之妙。因为分治中将问题分为若干子问题,但是大部分情况下子问题互相独立,而如果动态规划中大多数子问题不独立(即重叠子问题性质),我们同分治一样,采取从上到下再到上分析,从下到上求解的方法,保留每一个子问题的解(动态规划问题要符合最优子结构),由子解来得到上一层的解。所以求解要先找出最优解的性质,递归定义最优值,自底而上,由计算最优值时得到的信息构造最优解。

 

(动态规划有“牺牲空间换取时间”的意思,分治很有可能进行大量重复计算,动态规划要把已解决的问题子答案全部保存,以备后续使用)

 

有些问题很难想到解决方法,不要怕,很多人直接想不出来,这时候去搜一些解答或许对我们有所启发。:-D


矩阵连乘

给定{A1, A2, A3,..., An},其中A(i)与A(i+1)是可乘的,i=1,2,....n-1。求这n个矩阵的连乘积A1A2A3...An以最少的计算量完成计算时的最优计算次序。矩阵乘法是满足结合律。

例如:

A1 = 10*100

A2 = 100*5

A3 = 5*50

A[p0,p1]*A2[p1,p2]=A3[p0,p2]=>,数乘次数=p0*p1*p2。

如果((A1A2)A3)=10*100*5+10*5*50=7500

(A1(A2A3))=100*5*50+10*100*50=75000

由此可见不同计算次序所需数乘是大有不同的。

 

矩阵连乘可以用加括号来限定计算次序。

规定:

1.单个矩阵是加括号的。

2.矩阵连乘积A 是加括号的,那么A可以表示为两个加括号的矩阵B与C的乘积并加括号,即A=(BC)

用分治法穷举所有计算序列有大量重复计算,并且复杂度随n指数增长,过于低效。

 

求解:(抄书)

1.第一,刻画该问题最优解的结构特征。

将A(i)A(i+1)...Aj记为A[i:j],计算A[i:j]的最优计算次序,设这个计算次序在矩阵A(k)与A(k+1)之间将他断开,1<=k<=n,则其对应的完全加括号方式为((A1...Ak)(A(k+1)...An)).依次矩阵相乘先计算A[1:k]与A[k+1:n],再将计算结果相乘得到A[1:n].那么总计算量为A[1:k]与A[k+1,n]的计算量加上A[1:k]与A[k+1:n]相乘的计算量。由最优子结构可知A[1:k]与A[k+1,n]的计算次序也是最优的。

2.递归关系建立。

设计算A[i:j],1<=i<=j<=n,所需要的最少数乘次数为m[i][j],则原题最优解为m[1][n].

当i=j,A[i:j]=Ai(单个基本矩阵),m[i][i]=0 , i=1,2,...n;

当i<j,可借助最优子结构性质来计算m[i][j](这也就是第一步的目的)。

若A[i:j]在A(k)与A(k+1),i<k<j之间断开,那么m[i][j]=m[i][k]+m[k+1][j]+p(i-1)p(k)p(j)。

(P(i)为矩阵行或者列数)。 K in{i,i+1,i+2,...j_1}。所以k为这j-i个位置中计算量最小的那个。

所以m[i][j]可递归定义为

M[i][j] = 0 ,i == j

M[i][j] = min{m[i][k]+m[k+1][j]+p(i-1)p(k)p(j)} ,i<=k<=j, i<j

计算时可以保存断开位置k到另一个矩阵。

3.计算最优值。

自底而上。


可见动态规划可节省大量计算。

主要借助第二步推导

动态规划:


void MatrixChain(int *p,int n) {
       //p[i]为矩阵序列中矩阵[i]的行数==下一矩阵的列数
       //n==矩阵数,m记录计算数量,s记录断开位置
      
       for(int i=1; i<=n; ++i) m[i][i]=0;//单个矩阵计算次数为0
       for(int r=2; r<=n; ++r) {         // 每一斜行
              for(int i=1;i<=n-r+1; ++i) {  //行
                     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) {
                            //求解本次分组(含有j-i个元素的分组)中最小的计算次数
                            intt=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;
                            }
                     }
              }
       }
}


 

 

递归式Code,

int RecurMatrixChain(inti,int j) {
       //递归式并采取备忘录方法
       //需初始化m[i][j]=-1;
       if(m[i][j]>0) return m[i][j]; //备忘记录,以此减少递归计算量
       if(i==j) return 0;
       int u = RecurMatrixChain(i,i)+RecurMatrixChain(i+1,j)+p[i-1]*p[k]*p[j];
       s[i][j]=i;
       for(int k=i+1; k<j; ++k) {
              int t =RecurMatrixChain(i,k)+RecurMatrixChain(k+1,j)+p[i-1]*p[k]*p[j];
              if(t<u) {
                     u = t;
                     s[i][j]=k;
              }
       }
       m[i][j]=u;
       return u;
}


最长公共子序列

给定两个序列X、Y,求解他们的最长公共子序列。

对于序列X={x1,x2,x3....xn},另一序列Z={z1,z2,z3...}是X子序列的必要条件是每一个zi都属于X,并且zi对应在X中的相同数值的下标是递增的。


设序列X ={x1,x2,x3...xm} 、 Y ={y1,y2,y3....yn} 最长公共子序列Z={z1,z2,z3...zk}.则:

1.若 xm == yn ,有zk == xm ==yn,且Z(k-1)是X(m-1)与Y(n-1)的最长公共子序列(最优子结构)。若xm!=yn &&zk!=xm,有Z是X(m-1)与Y的最长公共子序列。

2.若xm!=yn && zk != yn,有Z是X与Y(n-1)的最长公共子序列。

X(m-1)={x1,x2,x3...x(m-1)}       Y(n-1)={y1,y2,y3...y(n-1)}  Z(k-1)={z1,z2,z3...z(k-1)}。

 

子问题递归结构:

要找X与Y的最长公共子序列,可以按照上面的推导,进行递归寻找。

用c[i][j]记录序列Xi、Yj 的最长公共子序列长度,Xi={x1,x2,x3..xi},Y={y1,y2,...yi}

显然当i==0 ||j==0时,空序列是二者最长公共子序列。即C[i][j] == 0。

那么

C[i][j] =  0                                ,i==0 || j==0

C[i-1][j-1]+1              ,i,j> 0 && xi != yi

Max{c[i][j-1],c[i-1][j]} ,i,j> 0 && xi != yi

 

int c[n+1][m+1];  //记录字串长度
int b[n+1][m+1];  //记录c[i][j]来源
 
void LCSLength(int n,intm,const char *x,const char *y) {
       //n序列x字符数,m序列y字符数
       for(int i=0;i<=n;++i) c[i][0]=0;
       for(int i=0;i<=m;++i) c[0][i]=0;
       for(int i=1;i<=n;++i) {
              for(intj=1;j<=m;++j) {
                     if(x[i-1]==y[j-1]) {
                            //x[i]==y[i],才是真正的在字串中
                            c[i][j]=c[i-1][j-1]+1;
                            b[i][j]=1;
                     } 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;
                     }
              }
       }
}
 
void LCS(int i,intj,const char *x) {
       if(i==0 || j==0) return ;
       if(b[i][j]==1) {  
              LCS(i-1,j-1,x);
              cout<<x[i-1];
       } else if(b[i][j]==2)
              LCS(i-1,j,x);
       else
              LCS(i,j-1,x);
}


 

 最大字段和

给定n个整数组成的序列a1,a2,...an,求该序列形如的子段和最大。

Max()   1<=i<=j<=n

 

第一我们可以遍历所有子段和来解决

int MaxSum(int n,inta[],int &start,int &end)
{
       //遍历法求最大子段和
       //n元素数,a元素数组
       //start选中的字段开始元素下标
       //end选中的字段结束元素下标
       int sum=0;
       for(int i=0;i<=n;++i) {
              int this_sum = 0;
              for(intj=i;j<=n;++j) {
                     this_sum += a[j];
                     if(this_sum>sum) {
                            sum  = this_sum;
                            start= i;
                            end= j;
                     }
              }
       }
       return sum;
}


第三动态规划分析

 

记b[j]为第1-j个元素的最大子段和,则所求最大子段和为b[n],

依据b[j]定义可知b[j-1]>0时b[j]=b[j-1]+a[j],否则b[j]=a[j].

即b[j]的动态规划递归式为

b[j]=max{b[j-1]+a[j],a[j] }, 1<=j<=n;

 

Code:

 

int MaxSum(int n,inta[])
{
       //动态规划求最大子段和
       int sum = 0,b= 0;
       for(int i=0;i<n;++i) {
              if(b>0)b+=a[i];
              else b = a[i];
              if(b>sum) sum =b;
       }
       return sum;
} 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值