最长公共子序列、最长公共子串、最小编辑距离三种算法总结
最长公共子序列、最长公共子串、最小编辑距离是三种常见的字符串比较算法,考虑到其中的动态规划思想、状态转移方程比较类似,实现的方法也是如出一辙,这里将其状态转移方程和相应的实现代码做一个总结。
1、最长公共子序列(longest common sequence)
状态转移方程:
实现代码:
1 int LCSeq(string str1, string str2) 2 { 3 if(str1.size()==0 || str2.size()==0)return 0; 4 //分配内存dp[str1.size()+1][str2.size()+1]; 5 int **dp=new int*[str1.size()+1]; 6 for(int i=0; i<str1.size()+1; ++i) 7 { 8 dp[i]=new int[str2.size()+1]; 9 } 10 //初始化数组 11 //dp[i][0]=0 and dp[0][j]=0; 12 for(int i=0; i<str1.size()+1; ++i)dp[i][0]=0; 13 for(int j=0; j<str2.size()+1; ++j)dp[0][j]=0; 14 //填数组 15 for(int i=1; i<str1.size()+1; ++i) 16 { 17 for(int j=1; j<str2.size()+1; ++j) 18 { 19 if(str1[i-1]==str2[j-1])dp[i][j]=dp[i-1][j-1]+1;//注意此处应减1,因为dp二维数组的长和宽都加了1 20 else dp[i][j]=max(dp[i-1][j], dp[i][j-1]); 21 } 22 } 23 int result=dp[str1.size()][str2.size()]; 24 //释放空间 25 for(int i=0; i<str1.size()+1; ++i) 26 { 27 delete [] dp[i]; 28 } 29 delete [] dp; 30 return result; 31 }
当然了,如果想把最长的公共子序列打印出来,也是可以的,只需要求出dp数组后,由右下角往左上角推就可以了。考虑到两个字符串的最长公共子序列可能有多个,所以有以下两种写法,分别打印一个和多个最长公共子序列:
1 void printOne(string str1, string str2, int **dp) 2 { 3 int i=str1.size(); 4 int j=str2.size(); 5 string tmp; 6 while(i>0 && j>0) 7 { 8 if(str1[i-1]==str2[j-1]){ 9 tmp=str1[i-1]+tmp; 10 --i; 11 --j; 12 }else{ 13 if(dp[i-1][j]>=dp[i][j-1]){ 14 --i; 15 }else{ 16 --j; 17 } 18 } 19 } 20 cout<<tmp<<endl; 21 } 22 bool isExist(vector<string> &result, string tmp) 23 { 24 for(int i=0; i<result.size(); ++i) 25 { 26 if(result[i]==tmp)return true; 27 } 28 return false; 29 } 30 void printAllCore(string &str1, string &str2, int **dp, int i, int j, vector<string> &result, string tmp) 31 { 32 if(i==0 || j==0){ 33 if(!isExist(result, tmp))result.push_back(tmp);//如果之前不存在就加入,避免重复 34 return; 35 } 36 if(str1[i-1]==str2[j-1]){ 37 printAllCore(str1, str2, dp, i-1, j-1, result, str1[i-1]+tmp); 38 }else{ 39 if(dp[i-1][j]>dp[i][j-1])printAllCore(str1, str2, dp, i-1, j, result, tmp); 40 else if(dp[i-1][j]<dp[i][j-1])printAllCore(str1, str2, dp, i, j-1, result, tmp); 41 else{ 42 printAllCore(str1, str2, dp, i-1, j, result, tmp); 43 printAllCore(str1, str2, dp, i, j-1, result, tmp); 44 } 45 } 46 } 47 void printAll(string str1, string str2, int **dp) 48 { 49 vector<string> result; 50 string tmp; 51 printAllCore(str1, str2, dp, str1.size(), str2.size(), result, tmp); 52 for(int i=0; i<result.size(); ++i)cout<<result[i]<<endl; 53 } 54 int LCSeq(string str1, string str2) 55 { 56 if(str1.size()==0 || str2.size()==0)return 0; 57 //分配内存dp[str1.size()+1][str2.size()+1]; 58 int **dp=new int*[str1.size()+1]; 59 for(int i=0; i<str1.size()+1; ++i) 60 { 61 dp[i]=new int[str2.size()+1]; 62 } 63 //初始化数组 64 //dp[i][0]=0 and dp[0][j]=0; 65 for(int i=0; i<str1.size()+1; ++i)dp[i][0]=0; 66 for(int j=0; j<str2.size()+1; ++j)dp[0][j]=0; 67 //填数组 68 for(int i=1; i<str1.size()+1; ++i) 69 { 70 for(int j=1; j<str2.size()+1; ++j) 71 { 72 if(str1[i-1]==str2[j-1])dp[i][j]=dp[i-1][j-1]+1;//注意此处应减1,因为dp二维数组的长和宽都加了1 73 else dp[i][j]=max(dp[i-1][j], dp[i][j-1]); 74 } 75 } 76 int result=dp[str1.size()][str2.size()]; 77 //printOne(str1, str2, dp);//打印一个 78 printAll(str1, str2, dp);//打印所有 79 //释放空间 80 for(int i=0; i<str1.size()+1; ++i) 81 { 82 delete [] dp[i]; 83 } 84 delete [] dp; 85 return result; 86 }
2、最长公共子串(longest common substring)
状态转移方程:
实现代码:
1 int LCSub(string str1, string str2) 2 { 3 if(str1.size()==0 || str2.size()==0)return 0; 4 //分配内存dp[str1.size()+1][str2.size()+1]; 5 int **dp=new int*[str1.size()+1]; 6 for(int i=0; i<str1.size()+1; ++i) 7 { 8 dp[i]=new int[str2.size()+1]; 9 } 10 //初始化数组 11 //dp[i][0]=0 and dp[0][j]=0; 12 for(int i=0; i<str1.size()+1; ++i)dp[i][0]=0; 13 for(int j=0; j<str2.size()+1; ++j)dp[0][j]=0; 14 //填数组 15 for(int i=1; i<str1.size()+1; ++i) 16 { 17 for(int j=1; j<str2.size()+1; ++j) 18 { 19 if(str1[i-1]==str2[j-1])dp[i][j]=dp[i-1][j-1]+1;//注意此处应减1,因为dp二维数组的长和宽都加了1 20 else dp[i][j]=0; 21 } 22 } 23 int max=0; 24 for(int i=0; i<str1.size()+1; ++i) 25 { 26 for(int j=0; j<str2.size()+1; ++j) 27 { 28 if(dp[i][j]>max)max=dp[i][j]; 29 } 30 } 31 //释放空间 32 for(int i=0; i<str1.size()+1; ++i) 33 { 34 delete [] dp[i]; 35 } 36 delete [] dp; 37 return max; 38 }
同样地,如果想打印最长公共子串,也是可以的,也有两种打印方式,分别打印一个和所有个:
1 void printOne(string str1, string str2, int **dp, int max) 2 { 3 for(int i=0; i<str1.size()+1; ++i) 4 { 5 for(int j=0; j<str2.size()+1; ++j) 6 { 7 if(dp[i][j]==max) 8 { 9 int m=i, n=j; 10 string tmp; 11 while(m-1>=0 && n-1>=0 && str1[m-1]==str2[n-1]) 12 { 13 tmp=str1[m-1]+tmp; 14 --m; 15 --n; 16 } 17 cout<<tmp<<endl; 18 return; 19 } 20 } 21 } 22 } 23 bool isExist(vector<string> &result, string tmp) 24 { 25 for(int i=0; i<result.size(); ++i) 26 { 27 if(result[i]==tmp)return true; 28 } 29 return false; 30 } 31 void printAll(string str1, string str2, int **dp, int max) 32 { 33 vector<string> result; 34 for(int i=0; i<str1.size()+1; ++i) 35 { 36 for(int j=0; j<str2.size()+1; ++j) 37 { 38 if(dp[i][j]==max) 39 { 40 int m=i, n=j; 41 string tmp; 42 while(m-1>=0 && n-1>=0 && str1[m-1]==str2[n-1]) 43 { 44 tmp=str1[m-1]+tmp; 45 --m; 46 --n; 47 } 48 if(!isExist(result, tmp))result.push_back(tmp);//去重 49 } 50 } 51 } 52 for(int i=0; i<result.size(); ++i)cout<<result[i]<<endl; 53 } 54 int LCSub(string str1, string str2) 55 { 56 if(str1.size()==0 || str2.size()==0)return 0; 57 //分配内存dp[str1.size()+1][str2.size()+1]; 58 int **dp=new int*[str1.size()+1]; 59 for(int i=0; i<str1.size()+1; ++i) 60 { 61 dp[i]=new int[str2.size()+1]; 62 } 63 //初始化数组 64 //dp[i][0]=0 and dp[0][j]=0; 65 for(int i=0; i<str1.size()+1; ++i)dp[i][0]=0; 66 for(int j=0; j<str2.size()+1; ++j)dp[0][j]=0; 67 //填数组 68 for(int i=1; i<str1.size()+1; ++i) 69 { 70 for(int j=1; j<str2.size()+1; ++j) 71 { 72 if(str1[i-1]==str2[j-1])dp[i][j]=dp[i-1][j-1]+1;//注意此处应减1,因为dp二维数组的长和宽都加了1 73 else dp[i][j]=0; 74 } 75 } 76 int max=0; 77 for(int i=0; i<str1.size()+1; ++i) 78 { 79 for(int j=0; j<str2.size()+1; ++j) 80 { 81 if(dp[i][j]>max)max=dp[i][j]; 82 } 83 } 84 //printOne(str1, str2, dp, max);//打印一个 85 printAll(str1, str2, dp, max);//打印所有 86 //释放空间 87 for(int i=0; i<str1.size()+1; ++i) 88 { 89 delete [] dp[i]; 90 } 91 delete [] dp; 92 return max; 93 }
3、最小编辑距离
之前做项目的时候用到过最小编辑距离算法,这里也总结一下。
状态转移方程:
实现代码:
1 int minEditDistance(string str1, string str2) 2 { 3 if(str1.size()==0)return str2.size(); 4 if(str2.size()==0)return str1.size(); 5 //分配内存dp[str1.size()+1][str2.size()+1]; 6 int **dp=new int*[str1.size()+1]; 7 for(int i=0; i<str1.size()+1; ++i) 8 { 9 dp[i]=new int[str2.size()+1]; 10 } 11 //初始化数组 12 //dp[0][0]=0; 13 //dp[i][0]=i and dp[0][j]=j; 14 for(int i=0; i<str1.size()+1; ++i)dp[i][0]=i; 15 for(int j=0; j<str2.size()+1; ++j)dp[0][j]=j; 16 //填数组 17 for(int i=1; i<str1.size()+1; ++i) 18 { 19 for(int j=1; j<str2.size()+1; ++j) 20 { 21 int flag=0; 22 if(str1[i-1]!=str2[j-1])flag=1;//注意此处应减1,因为dp二维数组的长和宽都加了1 23 dp[i][j]=min(dp[i-1][j]+1, dp[i][j-1]+1, dp[i-1][j-1]+flag); 24 } 25 } 26 int result=dp[str1.size()][str2.size()]; 27 //释放空间 28 for(int i=0; i<str1.size()+1; ++i) 29 { 30 delete [] dp[i]; 31 } 32 delete [] dp; 33 return result; 34 }