动态规划算法之矩阵连乘 及最长公共字符串多种解法源码
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];
}
}
}
计算三个矩阵连乘{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];
}
}
}
相关文章推荐
- 查找两个字符串的最长公共子串(附源码)_AX
- 寻找两个字符串中最长的公共子串源码(不使用strcmp)
- 转【算法之动态规划(三)】动态规划算法之:最长公共子序列 & 最长公共子串(LCS)&字符串相似度算法
- 面试宝典_Python.常规算法.0002.输出任意两个字符串中最长公共子串?
- 求多字符串的最长公共子串(POW)
- 字符串数组最长公共前缀
- 查找两个字符串a,b中的最长公共子串
- 华为oj 查找两个字符串a,b中的最长公共子串
- 序列试题---最大子序列、最长递增子序列、最长公共子串、最长公共子序列、字符串编辑距离 .
- LeetCode -- 求字符串数组中的最长公共前缀
- C语言中怎样查找两个字符串的最长公共字符串
- 矩阵连乘问题的非动态解法
- 华为OJ(查找两个字符串a,b中的最长公共子串)
- 求两个字符串的最长公共子串的长度
- 求两个字符串的最长公共子串
- 查找两个字符串a,b中的最长公共子串
- 最长公共字符串
- 北大OJ百练——4073:最长公共字符串后缀(C语言)
- 两个字符串的最长连续公共子串
- 获取两个字符串之间最长公共字符串的函数