LCS可以描述两段文字之间的“相似度”,即它们的雷同程度,从而能够用来辨别抄袭。另一方面,对一段文字进行修改之后,计算改动前后文字的最长公共子序列,将除此子序列外的部分提取出来,这种方法判断修改的部分,往往十分准确。
子序列:一个序列S任意删除若干个字符得到新序列T。
公共子序列:两个序列X和Y中都存在的项组成的序列。
最长公共子序列:两个序列X和Y的公共子序列中最长的序列。
例如:
字符串13455与245576的最长公共子序列为455
字符串acdfg与adfc的最长公共子序列为adf
第一种:自定义暴力求取方式
假定字符串X,Y的长度分别为m,n
X的一个子序列即下标序列{1, 2, …, m}的严格递增子序列,因此,X共有2m个不同子序列;同理,Y有2n个不同子序列,从而穷举搜索法需要指数时间O(2m*2n);
对X的每一个子序列,检查它是否也是Y的子序列,从而确定它是否为X和Y的公共子序列,并且在检查过程中选出最长的公共子序列;
显然,不可取。
针对该种算法进行改良,只对第一组数据进行依次列出,然后与第二组进行比较。其时间复杂度虽然也很高,但与2m*2n相比还是要少一些。
void lscChangeViolence(int *X, int *Y, int m) { int *AllX, *AllFlag, *result; AllX = (int*)malloc(sizeof(int)*m); AllFlag = (int*)malloc(sizeof(int)*m); result = (int*)malloc(sizeof(int)*m); for (int i = 0; i < m; i++) { AllX[i] = 0; AllFlag[i] = 0; result[i] = -1000; } int count = 1; while (count < pow(2, m)) { int num = 0; // 按照取某一位的值和不取某一位的值,将X中的某些值赋值给AllX for (int i = 0; i < m; i++) { if (((count >> i) & 1) == 1) { AllX[num] = X[i]; num++; } } // 给定比较数组 int *compare; compare = (int*)malloc(sizeof(int)*(num)); for (int i = 0; i < num; i++) { compare[i] = AllX[i]; } int comNum = 0; int jump = 0; int maxNum = -1; int start = 0; // 如果子数组与第二组匹配上,则记录长度,并记下数据 for (int i = 0; i < num; i++) { if (start > n) { break; } for (int j = start; j < n; j++) { if (compare[i] == Y[j]) { start = j + 1; comNum++; break; } } if (comNum == i) { break; } } if (comNum > maxNum) { maxNum = comNum; for (int k = 0; k < maxNum; k++) { result[k] = compare[k]; } } count++; } for (int i = 0; i < 5; i++) { if (result[i] != -1000) { printf("result[%d]=%d\n", i, result[i]); } } }第二种:动态规划法
字符串X,长度为m,从1开始数;
字符串Y,长度为n ,从1开始数;
LCS(X , Y) 为字符串X和Y的最长公共子序列。
若xm=yn(最后一个字符相同),则:Xm与Yn的最长公共子序列Zk的最后一个字符必定为xm或yn。
LCS(Xm , Yn) = LCS(Xm-1 , Yn-1) + xm
若xm≠yn,则:要么LCS(Xm,Yn)=LCS(Xm-1, Yn),要么LCS(Xm,Yn)=LCS(Xm, Yn-1)。
则:
使用二维数组C[m,n]。
c[i,j]记录序列Xi和Yj的最长公共子序列的长度。
当i=0或j=0时,空序列是Xi和Yj的最长公共子序列,故c[i,j]=0。
使用二维数据B[m,n],其中,b[i,j]标记c[i,j]的值是由哪一个子问题的解达到的。即c[i,j]是由c[i-1,j-1]+1或者c[i-1,j]或者c[i,j-1]的哪一个得到的。取值范围为Left,Top,LeftTop三种情况。
// 根据规则将c[i][j]进行赋值,获取方向数组b[i][j] void lcs_length(char *X, char *Y, int mLength, int nLength,int **C,char **B) { int num = 0; for (int i = 1; i < mLength + 1; i++) { for (int j = 1; j < nLength + 1; j++) { if (X[i-1] == Y[j-1]) { C[i][j] = C[i - 1][j - 1] + 1; B[i][j] = 'x'; } else if (C[i - 1][j] >= C[i][j - 1]) { C[i][j] = C[i - 1][j]; B[i][j] = 's'; } else { C[i][j] = C[i][j - 1]; B[i][j] = 'z'; } } } for (int i = 0; i < mLength + 1; i++) { for (int j = 0; j < nLength + 1; j++) { printf("C[%d][%d]=%d ", i, j, C[i][j]); } printf("\n"); } }下面打印最长公共子序列
// 从最后的位置进行索引,按照方向依次寻找数据,将标记为x的数据打印即为最长子数组 void lcsPrint(char **B, char *X, int i, int j) { if (i == 0 || j == 0) { return; } if (B[i][j] == 'x') { lcsPrint(B, X, i - 1, j - 1); printf("X[%d]=%c\n", i - 1, X[i - 1]); } else if (B[i][j] == 's') { lcsPrint(B, X, i - 1, j); } else { lcsPrint(B, X, i, j - 1); } }参考资料:
七月算法: https://www.julyedu.com/