如果一个问题由交叠的子问题所构成,可以采用动态规划的方法进行求解。
交叠的子问题可以理解为要想求解当前问题必须知道前一问题的解,也就是问题之间不是相互独立的。
动态规划一般分为以下几步进行:
1.找出最优解性质,刻画其结构特征;
2.递归定义最优解,写出动态规划方程;
3.以自底向上的方式计算出最优解;
4.根据计算得到的信息,构造最优解。
下面有一个LCS(最长公共子序列)的例子。
第一步找最优解性质,很明显子序列长度可以表征其最优解的性质。
第二步写出动态规划方程
公式编辑器那么大的括号,各位看官将就下,主要看内容。
len(i,j) = 0 if i=0 or j=0;
len(i,j) = len(i-1,j-1) if i>0,j>0 and ai=bj
len(i,j) = max(len(i,j-1),len(i-1,j)) if i>0,j>0 and ai!=bj
第三步求解最优解
#include<iostream>
#include<string>
#include<stack>
using namespace std;
stack<char> squence;
void getLCS(int **&tmp1,int **&tmp2,int len1,int len2,string sq1,string sq2){
//两个矩阵 一个存长度 一个存方向
tmp1 = (int **)malloc((len1+1)*sizeof(int *));
for(int i=0;i<=len1;++i){
tmp1[i] = (int *)malloc((len2+1)*sizeof(int));
}
tmp2 = (int **)malloc((len1+1)*sizeof(int *));
for(int i=0;i<=len1;++i){
tmp2[i] = (int *)malloc((len2+1)*sizeof(int));
}
for(int i=0;i<=len1;++i){//标明边界
tmp1[i][0] = 0;
tmp2[i][0] = 0;
}
for(int i=0;i<=len2;++i){//0表示边界 不指向任何方向 1向左,2向右,3向上
tmp1[0][i] = 0;
tmp2[0][i] = 0;
}
for(int i=1;i<=len1;++i)
for(int j=1;j<=len2;++j){
if(sq1.at(i-1)==sq2.at(j-1)){
tmp1[i][j] = tmp1[i-1][j-1] +1;
tmp2[i][j] = 2;
}
else {
if(tmp1[i-1][j]>tmp1[i][j-1]){
tmp1[i][j] = tmp1[i-1][j];
tmp2[i][j] = 3;//指向上
}
else{
tmp1[i][j] = tmp1[i][j-1];
tmp2[i][j] = 1;//指向左
}
}
}
}
void outpuLCS(string sq1,int **tmp,int len1,int len2){
if(len1==0||len2==0)return;
if(tmp[len1][len2]==2){
//cout<<sq1.at(len1-1);
squence.push(sq1.at(len1-1));
outpuLCS(sq1,tmp,len1-1, len2-1);
}
else if(tmp[len1][len2]==1){
outpuLCS(sq1,tmp,len1, len2-1);
}
else {
outpuLCS(sq1,tmp,len1-1, len2);
}
}
int main(){
int **len,**path;
string str1,str2;
str1 = "abcdefg";
str2 = "bcgefd";
len = NULL;path = NULL;
getLCS(len,path,str1.size(),str2.size(),str1,str2);
outpuLCS(str1,path,str1.size(),str2.size());
while(!squence.empty()){
cout<<squence.top();
squence.pop();
}
cout<<endl;
free(len);free(path);
}
第四步根据方向信息构造最优解。
需要说明的是有时候最优解并不唯一,但是在这里并没有说明,感兴趣的朋友可以研究下。
有时间了我会回过头来贴出求解所有最优解的代码。
下面我们接着分析其时间复杂度:
将getLCS函数中的比较最后一个双层循环的比较语句视为关键语句,得到时间复杂度为:
所以动态规划的时间复杂度为O(mn)。
有木有感觉有点意思?下面再来一个最短公共超序列的求解代码,相信有了前面的基础这里就不需要再进行详细的说明。
这里说明一下最短公共超序列,也就是同时包含所有子序列信息的一个最短的序列,可以理解为子序列的不重读融合。
代码如下:
#include<iostream>
#include<string>
#include<stack>
using namespace std;
stack<char> squence;
void getSCS(int **&tmp1,int **&tmp2,int len1,int len2,string sq1,string sq2){
//两个矩阵 一个存长度 一个存方向
tmp1 = (int **)malloc((len1+1)*sizeof(int *));
for(int i=0;i<=len1;++i){
tmp1[i] = (int *)malloc((len2+1)*sizeof(int));
}
tmp2 = (int **)malloc((len1+1)*sizeof(int *));
for(int i=0;i<=len1;++i){
tmp2[i] = (int *)malloc((len2+1)*sizeof(int));
}
for(int i=0;i<=len1;++i){//标明边界
tmp1[i][0] = i;
tmp2[i][0] = 0;
}
for(int i=0;i<=len2;++i){//0表示边界 不指向任何方向 1向左,2向右,3向上
tmp1[0][i] = i;
tmp2[0][i] = 0;
}
for(int i=1;i<=len1;++i)
for(int j=1;j<=len2;++j){
if(sq1.at(i-1)==sq2.at(j-1)){
tmp1[i][j] = tmp1[i-1][j-1] +1;
tmp2[i][j] = 2;
}
else {
if(tmp1[i-1][j]>tmp1[i][j-1]){
tmp1[i][j] = tmp1[i][j-1]+1;
tmp2[i][j] = 1;//指向左
}
else{
tmp1[i][j] = tmp1[i-1][j]+1;
tmp2[i][j] = 3;//指向上
}
}
}
}
void outpuSCS(string sq1,string sq2,int **tmp,int len1,int len2){
if(len1==0){
while(len2--)squence.push(sq2.at(len2));
return;
}
if(len2==0){
while(len1--)squence.push(sq1.at(len1));
return;
}
if(tmp[len1][len2]==2){
//cout<<sq1.at(len1-1);
squence.push(sq1.at(len1-1));
outpuSCS(sq1,sq2,tmp,len1-1, len2-1);
}
else if(tmp[len1][len2]==1){
squence.push(sq2.at(len2-1));
outpuSCS(sq1,sq2,tmp,len1, len2-1);
}
else {
squence.push(sq1.at(len1-1));
outpuSCS(sq1,sq2,tmp,len1-1, len2);
}
}
int main(){
int **len,**path;
string str1,str2;
str1 = "ABCBDAB";
str2 = "BDCABA";
len = NULL;path = NULL;
getSCS(len,path,str1.size(),str2.size(),str1,str2);
outpuSCS(str1,str2,path,str1.size(),str2.size());
while(!squence.empty()){
cout<<squence.top();
squence.pop();
}
cout<<endl;
for(int i=0;i<=str1.size();++i){
for(int j=0;j<=str2.size();++j)
cout<<len[i][j]<<" ";
cout<<endl;
}
cout<<endl;
for(int i=0;i<=str1.size();++i){
for(int j=0;j<=str2.size();++j)
cout<<path[i][j]<<" ";
cout<<endl;
}
free(len);free(path);
}
若文章有什么问题,欢迎大家指正,共同学习共同进步。