动态规划的基本思想是:将求解的问题分解成若干个子问题,先求解子问题,然后再从这些子问题的解得到原问题的解。与分治法的区别是,适合用动态规划解决的问题,经分解得到的子问题往往不是相互独立的。动态规划将问题分解成子问题,但是子问题不相互独立,而是彼此依赖,相互提供帮助,很好的利用了子问题的结构信息。
动态规划的步骤是:
1.找出最优解的特征,并刻画其结构特征;
2.递归地定义最优值;
3.以自底向上的方式计算出最优值;
4.根据计算最优值的时得到的信息,构造最优解。
动态规划算法的有效性依赖于两个最重要的性质:最优子结构性质和重叠子问题性质。
栗子1,矩阵连乘问题。
1 //动态规划——矩阵连乘问题 2 #include<iostream> 3 #include<cstring> 4 5 using namespace std; 6 int p[7]={30,35,15,5,10,20,25};//6个矩阵 7 int m[6][6];//存放计算量的矩阵 8 int s[6][6];//存放分割点的矩阵 9 10 void matrixChain()//第三步:计算最优值(前面两步没法再代码中体现,是构思的过程) 11 //生成断点矩阵的函数,计算出最优值,返回s矩阵 12 { 13 int length=6; 14 memset(m,0,sizeof(m)); 15 memset(s,0,sizeof(s)); 16 for(int r=2;r<=length;r++)//r表示的是计算的范围,开始只计算两个矩阵的计算量,然后依次计算3个,4个一直到目标的6个 17 //这是动态规划自底向上的体现,从最小的单位开始计算 18 { 19 for(int i=1;i<=length-r+1;i++)//i,j分别表示当前所计算的范围 20 { 21 int j=i+r-1; 22 m[i][j]=m[i][i]+m[i+1][j]+p[i]*p[i-1]*p[j];//因为后面要求最优值,所以这里其实是随便求一个值 23 s[i][j]=i; 24 for(int k=i+1;k<j;k++)//范围内优化,找到最小的值 25 { 26 int temp=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]; 27 if(temp<m[i][j]) 28 { 29 m[i][j]=temp; 30 s[i][j]=k; 31 } 32 } 33 } 34 } 35 } 36 void TraceBack(int i,int j)//第4步:构造最优解。输入s矩阵和想要求解的范围,输出最佳的括号方案 37 { 38 if(i==j) 39 { 40 cout<<"M"<<"["<<i<<"]"; 41 return; 42 } 43 cout<<"("; 44 TraceBack(i,s[i][j]); 45 TraceBack(s[i][j]+1,j); 46 cout<<")"; 47 } 48 int main() 49 { 50 matrixChain(); 51 TraceBack(1,6); 52 cout<<m[1][6]<<endl; 53 return 0; 54 }
栗子2:最长公共子序列
1 //动态规划-最长公共子序列 2 #include<iostream> 3 using namespace std; 4 char x[8]={'Z','A','B','C','B','D','A','B'};//函数中根本不会计算x[0]和y[0],这两个位置的字母只是为了占坑用 5 char y[7]={'Z','B','D','C','A','B','A'}; 6 int c[8][7]={0};//子序列的长度 7 int b[8][7]={0};//子序列以哪种方式可以找到它最长子序列。递归方式 8 //计算最优值 9 void lcsLength() 10 { 11 int m=7;//第一个序列的长度 12 int n=6;//第二个序列的长度 13 for(int i=1;i<=m;i++) 14 { 15 for(int j=1;j<=n;j++) 16 { 17 if(x[i]==y[j]) 18 { 19 c[i][j]=c[i-1][j-1]+1; 20 b[i][j]=1; 21 } 22 else 23 { 24 if(c[i-1][j]>c[i][j-1]) 25 { 26 c[i][j]=c[i-1][j]; 27 b[i][j]=2; 28 } 29 else 30 { 31 c[i][j]=c[i][j-1]; 32 b[i][j]=3; 33 } 34 35 } 36 37 } 38 } 39 } 40 //构造最优解 41 void lcs(int i,int j)//输出子序列和其长度 42 { 43 if(i==0||j==0) 44 return; 45 if(b[i][j]==1) 46 { 47 lcs(i-1,j-1); 48 cout<<x[i]; 49 } 50 else 51 { 52 if(b[i][j]==2) 53 { 54 lcs(i-1,j); 55 } 56 else 57 { 58 lcs(i,j-1); 59 } 60 61 } 62 63 64 65 } 66 int main() 67 { 68 lcsLength(); 69 lcs(7,6); 70 cout<<c[7][6]<<endl;//子序列的最大长度 71 return 0; 72 }
在牛客上提交的版本,牛客只需要输出最长公共子序列的长度
1 //动态规划-最长公共子序列 2 #include<iostream> 3 using namespace std; 4 void lcsLength(char x[],char y[],int m,int n) 5 { 6 int c[m+1][n+1];//子序列的长度 7 for(int i=0;i<m+1;i++) 8 { 9 for(int j=0;j<n+1;j++) 10 c[i][j]=0; 11 } 12 int b[m+1][n+1];//子序列以哪种方式可以找到它最长子序列。递归方式 13 for(int i=0;i<m+1;i++) 14 { 15 for(int j=0;j<n+1;j++) 16 b[i][j]=0; 17 } 18 for(int i=1;i<=m;i++) 19 { 20 for(int j=1;j<=n;j++) 21 { 22 if(x[i]==y[j]) 23 { 24 c[i][j]=c[i-1][j-1]+1; 25 b[i][j]=1; 26 } 27 else 28 { 29 if(c[i-1][j]>c[i][j-1]) 30 { 31 c[i][j]=c[i-1][j]; 32 b[i][j]=2; 33 } 34 else 35 { 36 c[i][j]=c[i][j-1]; 37 b[i][j]=3; 38 } 39 40 } 41 42 } 43 } 44 cout<<c[m][n]<<endl; 45 } 46 void lcs() 47 { 48 char a[1024]; 49 a[0]='Z'; 50 char b[1024]; 51 b[0]='Z'; 52 string str1; 53 string str2; 54 while(cin>>str1>>str2) 55 { 56 for(int i=0;i<str1.size();i++) 57 a[i+1]=str1[i]; 58 for(int i=0;i<str2.size();i++) 59 b[i+1]=str2[i]; 60 lcsLength(a,b,str1.size(),str2.size()); 61 } 62 } 63 int main() 64 { 65 lcs(); 66 return 0; 67 }