动态规划算法是一种常见的较高级算法,又叫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][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];
}
}
}