您的位置:首页 > 其它

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

2012-12-17 01:24 239 查看
动态规划算法是一种常见的较高级算法,又叫DP算法,其本质是动态查表,和递归分治的区别是,其子问题具有相关性,且具有最优子结构,因此需要表格进行记录。下面的例子是动态规划中的典型问题:矩阵连乘。
计算三个矩阵连乘{A1,A2,A3};维数分别为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 //k从i+1到j-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},找出X和Y的最长公共子序列。

解法:可以转换为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]
!=-1)

{

returnp[m]
;

}

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

{

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

}

else

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

returnp[m]
;

}

//遍历输出公共子串

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

{

if(m<1 ||n<1)

return;

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

{

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

}

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

{

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

}

else

{

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

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

{

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

}

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: