字符串的相似性:如果将一个串转换成为另一个串所需的操作数最少,那么可以说这两个串是相似的。另外一种权衡的方法是,寻换第三个串s3,如果s3都出现在s1和s2中,且出现的顺序相同,但不要求在s1和s2中连续,那么s3的长度越大,就说明相似度越高。
后一种对相似度概念命名为最长公共子序列。
1、最长公共子序列的特征
如果用暴力搜索的方法求解LCS问题,就要穷举X的所有子序列,对每个子序列进行检查,看它是否是Y的子序列,记录找到的最长的子序列。X对应下标人格集合{1,2,3……m}的一个子集,那么X的子序列就有2^m个。
但其实LCS是具有最优子结构的:
令X={x1,x2,x3……xm}, Y={y1,y2,y3……yn}。Z={z1,z2,z3……zk}为X和Y的任意LCS.则有:
(1)如果xm=yn, 则zk = xm = yn,且Zk-1是Xm-1与Yn-1的LCS.
(2)如果xm!=yn,则zk!=xm意味着Zk-1是Xm-1与Y的LCS
(3)如果xm!=yn,则zk!=yn意味着Zk-1是Yn-1与X的LCS
2、递归解
用dp[i][j]表示Xi与Yj的LCS的长度。
dp[i][j] = 0 if i = 0 or j =0
dp[i][j] = dp[i-1][j-1] +1 if xi = yj
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) if x1!=yj
代码如下:
-
-
-
- #include "stdafx.h"
- #include <string.h>
- #include <iostream>
-
- using namespace std;
-
- #define MaxLength 100
-
- int max(int a,int b)
- {
- return a>=b?a:b;
- }
-
-
- void LCS_Length(char x[], char y[], int m, int n, int c[][MaxLength], int b[][MaxLength])
- {
- for(int i=0;i<m;i++)
- c[i][0] = 0;
- for(int j=0;j<n;j++)
- c[0][j] = 0;
- for(int i=1;i<=m;i++)
- {
- for(int j=1;j<=n;j++)
- {
- if(x[i-1] == y[j-1])
- {
- c[i][j] = c[i-1][j-1] + 1;
- b[i][j] = 0;
- }
- else if(c[i-1][j]>=c[i][j-1])
- {
- c[i][j] = c[i-1][j];
- b[i][j] = 1;
- }
- else
- {
- c[i][j] = c[i][j-1];
- b[i][j] = -1;
- }
-
- }
- }
- cout<<"LCS = "<<c[m][n]<<endl;
- }
-
- int LCS(char x[],char y[],int m,int n, int c[][MaxLength])
- {
- int i = 0, j =0;
- for(i = 0;i<m;i++)
- c[i][0] = 0;
- for(j = 0;j<n;j++)
- c[0][j] = 0;
- for(i = 1 ;i<=m;i++)
- {
- for(j = 1;j<=n;j++)
- {
- if(x[i-1]==y[j-1])
- c[i][j] = c[i-1][j-1]+1;
- else
- c[i][j] = max(c[i-1][j],c[i][j-1]);
- }
- }
- return c[m][n];
- }
- void Print_LCS(int b[][MaxLength], char x[], int m, int n)
- {
- if(m==0||n==0)
- return;
- if(b[m][n] == 0)
- {
- Print_LCS(b,x,m-1,n-1);
- printf("%c ",x[m-1]);
- }
- else if(b[m][n] == 1)
- Print_LCS(b,x,m-1,n);
- else
- Print_LCS(b,x,m,n-1);
- }
- int _tmain(int argc, _TCHAR* argv[])
- {
- char x[8] = "abcbdab";
- char y[7] = "bdcaba";
- int c[MaxLength][MaxLength], b[MaxLength][MaxLength];
- LCS_Length(x,y,7,6,c,b);
- Print_LCS(b,x,7,6);
- cout<<"LCS = "<<LCS(x,y,7,6,c);
- return 0;
- }
那么对于字符串相似度的前一种解释,延伸出这样一道算法题目:
计算字符串的相似度,定义了一套操作方法,把两个不同的字符串变得相同,具体操作方法:
(1) 修改一个字符;(2) 增加一个字符;(3) 删除一个字符。
如果所进行的操作数越少,相似度就越高,那么这道题目就转换成了,将两个字符串变为相同的字符串,所需的最小操作数。
对于两个字符:A=xabcdae, B=xfdfa。它们的第一个字符是相同的,那么只需要计算A[2……7]和B[2……5]的距离就可。如果第一个字符不同,那么可以进行这样的操作:
(1)删除A串的第一个字符,然后计算A[2->lenA]与B[1->lenB]。
(2)删除B串的第一个字符,然后计算A[1->lenA]与B[2->lebB]。
(3)修改A串的第一个字符为B串的第一个字符,然后计算A[2……lenA]与B[2……lenB]
(4)修改B串的第一个字符为A串的第一个字符,然后计算A[2……lenA]与B[2……lenB]
(5)增加A串的第一个字符到B串的第一个字符前,然后计算A[1……lenA]与B[2……lenB]
(6)增加B串的第一个字符到A串的第一个字符前,然后计算A[2……lenA]与B[1……lenB]
即,经过一步操作后,将A[1->lenA]与B[2->lenB]变为相同的字符串;将A[2->lenA]与B[1->lenB]变为相同的字符串;将A[2->lenA]与B[2->lenB]变为相同的字符串。这样求最小的操作数,就是求这三种情况的最小值。
令dp[i][j]表示A从第i个字符开始,B从第j个字符开始,所需要的操作数。
那么if A[i]!=B[j] , dp[i][j] = min(dp[i+1][j] , dp[i][j+1], dp[i+1][j+1]) +1
else dp[i][j] = dp[i+1][j+1]
部分代码如下:
- int DP(int n, int m)
- {
- int i = 0,j=0;
-
- for(i=0;i<n;i++)
- dp[i][m] = MaxLength;
- for(j=0;j<m;j++)
- dp[n][j] = MaxLength;
-
- for(i = n-1;i>0;i--)
- {
- for(j = m-1;j>0;j--)
- {
- if(X[i]==Y[j])
- dp[i][j] = dp[i+1][j+1];
- else
- dp[i][j] = min(dp[i+1][j],dp[i][j+1],dp[i+1][j+1])+1;
- }
- }
-
- return dp[0][0];
-
- }