动态规划算法之矩阵连乘 及最长公共字符串多种解法源码

动态规划算法是一种常见的较高级算法,又叫DP算法,其本质是动态查表,和递归分治的区别是,其子问题具有相关性,且具有最优子结构,因此需要表格进行记录。下面的例子是动态规划中的典型问题:矩阵连乘。

计算三个矩阵连乘{A1A2A3};维数分别为10*100 , 100*5 , 5*50

按此顺序计算需要的次数((A1*A2*A3:10X100X5+10X5X50=7500

按此顺序计算需要的次数(A1*A2*A3)):10X5X50+10X100X50=75000

所以问题是:如何确定运算顺序,可以使计算量达到最小化。

利用动态规划的思想,需要自下而上地建立最优子结构,怎样用程序实现遍历,有三种方法可以采用:

1)备忘录法

private static int lookupChain(int i, int j)

{

if (m[i][j] > 0) return m[i][j];

if (i == j) return 0;

int u = 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;}

}

m[i][j] = u;    //记录下最优值

p [i][j] = k;    //记录下最优位置

return u;

}

优点:递归写法,简单易懂

缺点:没有(好吧,我承认我没找出缺点)

 

2)手动循环,对角线遍历

思想:在求一个最优解的时候,保证其子集的最优解已经得出。简单理解就是先找出所有间隔为2的子集,在此基础上找出间隔为3的……直至N.

void matrixChain(){

11    for(int i=1;i<=n;i++)m[i][i]=0;

12

13    for(int r=2;r<=n;r++)//对角线循环

14        for(int i=1;i<=n-r+1;i++){//行循环

15            int j = r+i-1;//列的控制

16            //m[i][j]的最小值,先初始化一下,令k=i

17            m[i][j]=m[i][i]+m[i+1][j]+p[i-1]*p[i]*p[j];

18            s[i][j]=i;

19            //ki+1j-1循环找m[i][j]的最小值

20            for(int k = i+1;k<j;k++){

21                int temp=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];

22                if(temp<m[i][j]){

23                    m[i][j]=temp;

24                    //s[][]用来记录在子序列i-j段中,在k位置处

25                    //断开能得到最优解

26                    s[i][j]=k;

27                }

28            }

29        }

30}

 

3.手动循环之从左到又遍历。第一层循环从左往右,第二层在第一层中从右往左,从而保证每次的最优解可以为后面的计算服务。

public static void matrixChain(int [] p, int [][] m, int [][] s)

{

int n=p.length-1;

for (int i = 0; i <= n; i++) m[i][i] = 0; 

for (int i = 1; i <= n; i++)  

for (int j =i-1; j>=0;j--) {   //从对贴近的开始计算

m[j][i] = m[j][j]+ m[j+1][i]+ p[j-1]*p[j]*p[i];

s[j][i] = j;

for (int k = i+1; k <i; k++) {

//第一个子集第一层循环确保已有最优解,第二个子集第二层循环确保已有最优解

int t = m[j][k] + m[k+1][i] + p[j-1]*p[k]*p[i]; if (t < m[j][i]) {

m[j][i] = t;

s[j][i] = k;}

}

}

优点:好像,符合一贯的遍历思想

缺点:循环逻辑有些复杂。

 

呼呼!为了理解循环的算法,前后花了至少3小时!总结一下,我觉得如果可以理解熟练使用的话,动态规划的循环应该没什么问题了。

最长公共子序列:

给定2个序列X={x1,x2,,xm}Y={y1,y2,,yn},找出XY的最长公共子序列。

解法:可以转换为P(m,n)问题,

      (1) xm=yn, p(m,n)= 1+p(m-1,n-1);

            (2)xm!=yn, p(m,n)= max{p(m-1,n),p(m,n-1)};

//手动写循环

int p[10][10];

void getLongSub(char*a,  int m,char* b,int n)

{

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

         {

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

                   {

                            if (a[i] == b[j])

                            {

                                     p[i+1][j+1]=p[i][j]+1;

                            }

                            else

                            {

                                     p[i+1][j+1]= (p[i+1][j]>=p[i][j+1])?p[i+1][j]:p[i][j+1];

                            }

                   }

         }

}

 

//只用两行数组做记录表格,能得到最大长度,但是会丢失字符串信息

void getLongSub(char*a,  int m,char* b,int n)

{

        

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

         {

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

                   {

                            p[0][i] = p[1][i];

                            p[1][i] = 0;

                   }

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

                   {

                            if (a[i] == b[j])

                            {

                                     p[1][j+1]=p[0][j]+1;

                            }

                            else

                            {

                                     p[1][j+1] =(p[1][j]>=p[0][j+1])?p[1][j]:p[0][j+1];

                            }

                   }

         }

}

 

//利用递归

int getLongSub(char*a,  int m,char* b,int n)

{

 

         if(m<1 ||n<1)

                   return0;

         if(p[m][n]!=-1)

         {

                   returnp[m][n];

         }

         if(a[m-1] == b[n-1])

         {

                   p[m][n]=getLongSub(a,m-1,b,n-1)+1;

         }

         else

                   p[m][n] =max(getLongSub(a,m,b,n-1),getLongSub(a,m-1,b,n));

         returnp[m][n];

}

 

//遍历输出公共子串

void traverse(char*a,int m ,char*b, int n)

{

         if(m<1 ||n<1)

                   return;

         if(p[m][n] == p[m][n-1])

         {

                   traverse(a,m,b,n-1);

         }

         else if (p[m][n] == p[m-1][n])

         {

                   traverse(a,m-1,b,n);

         }

         else

         {

                   traverse(a,m-1,b,n-1);

                   if(p[m][n]== p[m-1][n-1]+1 )

                   {

                            cout << " "<<a[m-1];

                   }

         }

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值