动态规划——ACM兴趣小组辅导

来源:互联网 发布:汽车门户网站源码 编辑:程序博客网 时间:2024/05/01 22:26

一、矩阵连乘问题

1.两个矩阵乘积所需的计算量

void matrixMultiply(int **a, int **b, int **c, int ra, int ca, int rb, int cb)

{

   if(ca!=rb) return;

   for(int i=0; i<ra; ++i)

     for(int j=0; j<cb; ++j)

     {

         int sum = a[i][0] * b[0][j];

        for(int k=1; k<ca;++k)

            sum += a[i][k] * a[k][j];

         c[i][j] = sum;

      }

}

可见需三重循环,总共要ra*ca*cb次数乘。

2.矩阵连乘时,加括号方式影响整个计算量。A1A2A3(10×100, 100×5, 5×50),如(A1A2)A3需要7500次,而A1(A2A3)需要75000次,两者相差10倍。矩阵连乘问题就是求A1A2...An个矩阵的计算次序使得所需计算量最少,其中矩阵的行列存储在数据p中,A1为p0×p1,A2为p1×p2

3.分析

设计算A[i:j],1≤i≤j≤n,所需的最少数乘次数为m[i][j],则显然原问题的最少数乘次为m[1][n]。

若计算Ai...Aj时在Ak和Ak+1之间断开,则m[i][j]=m[i][k]+m[k+1][j]+pi-1pkpj。因为如果要想整个计算量最少,则Ai...Ak和Ak+1...Aj的计算量也必须最少。只要确定的k的位置,则就找到了一个断点。k只有i, i+1,...,j-1这几种可能。m[i][j]可递归地定义为:

4.利用递归直接计算a[i][j]。(其中s[i][j]保存的是断开点)

int RecurMatrixCahin(int i, int j)
{
    if(i == j) return 0;
    int u = RecurMatrixCahin(i,i)+RecurMatrixCahin(i+1,j)+p[i-1]*p[i]*p[j];
    s[i][j] = i;
    for(int k=i+1; k<j;++k)
   {
       int t = RecurMatrixCahin(i,k)+RecurMatrixCahin(k+1,j)+p[i-1]*p[k]*p[j];
       if(t < u)
      {
          u = t;
          s[i][j] = k;
      }
    }
    return u;
}

问题是:太多的子问题被重复计算。效率太低。2n

5.动态规划方法

void MatrixChain(int *pint nint **mint **s)
{
        for (int i = 1; i <= n; i++) m[i][i] = 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++) {
                 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;}
              }
          }
}
测试数据为: 
A1
A2
A3
A4
A5
A6
30´35
35´15
15´5
5´10
10´20
20´25
可见,动态规划方法相同的子问题只计算一次,效率高。n2
5.备忘录方法
int MemoizeMatrixChain(int n, int **m, int **s)
{
    for(int i=1;i<=n;++i)
      for(int j=1;j<=n;++j) m[i][j]=0;
    return LookupChain(1, n);
}
int LookupChain(int i, int j)
{
     if(m[i][j]>0) return m[i][j];
     if(i==j) return 0;
     int u = LookupChain(i,i)+LookupChain(i+1,j)+p[i-1]*p[i]*p[j];
    s[i][j] = i;
    for(int k=i+1; k<j;++k)
   {
       int t = LookupChain(i,k)+LookupChain(k+1,j)+p[i-1]*p[k]*p[j];
      if(t < u)
     {
        u = t;
        s[i][j] = k;
     }
   }
   return u;
}
此方法为动态规划方法的变形。当一个问题的所有子问题都至少要解一次时,用动态规划算法比备忘录方法好。当子问题空间中的部分子问题可不必求解时,用备忘录方法则较有利。
输出结果可用: 
void TraceBack(int i,int j,int**s)
{
if(i==j )  return;
TraceBack(i, s[i][j],s);
    TraceBack(s[i][j] +1,j,s);
cout<<"Multiply A"<<i<<","<<s[i][j]<<"and A"<<(s[i][j]+1)<<","<<j<<endl;
} 
以上内容摘自《计算机算法设计与分析》王晓东。
附:
void TraceBack(int i,int j,int**s)
{
if(i==j )
{ cout<<"A"<<i;
return;
}
int k=s[i][j];
if(i !=k)
cout<<'(';
    if(i !=k)
cout<<')';
if(j !=k+1)
cout<<'(';
TraceBack(k+1,j,s);
if(j !=k+1)
cout<<')';
}
TraceBack(i,k,s);