从本文开始要讲一个非常重要的问题:文本相似度问题,通过计算两个文本相似度,以达到文本的判重的目的。它的应用有很多,比如文本去重、搜索引擎网页判重、论文的反抄袭等。
![b64a91e96398c0f4937edbef6bd4dc2c.png](https://i-blog.csdnimg.cn/blog_migrate/99a1bc2fd6ccb2981c59358daf62ed88.jpeg)
解决文本相似度问题的算法有很多,本文就先讲一个比较简单的,基于最长公共子串的算法。
如果要比较两篇文档的相似度,可以将两篇文档看出是两个字符串序列,将其统一归一化处理后,可以转化为最长公共子串的问题。那么解决最长公共子串的算法又有很多,下面将逐个讲解。
一. 暴力解法
直接通过两个循环枚举第一个字符串的子串,然后判断其在第二个字符串中是否包含,并记录包含第一个字符串子串的最大长度即可。很明显,这样的解法时间复杂度是大于O(nm)的,代码如下
int LS(string x, string y) { int maxLen = 0; int xlen = x.size(); int ylen = y.size(); for (int i = 0; i < xlen; i++) { int len = 0; string str = ""; for (int j = i; j < ylen; j++) { str += x[j]; if (y.find(str) == y.npos) { break; } len++; } maxLen = max(maxLen, len); } return maxLen;}
这里在判断是否包含子串时用到了STL的find函数,它的平均时间复杂度为O(m+n),但最坏时间复杂度为O(mn),可以将其改为KMP匹配算法,那么整体时间复杂度为O(m*n*(m+n))。这种暴力算法性能太差,那么下面基于动态规划的方法性能又要好很多。
![7c0dbb86f55c4cf268794e22ac124eff.png](https://i-blog.csdnimg.cn/blog_migrate/c7e38929791c164ef0e5de9d25e672b4.jpeg)
二. 动态规划法
假如我们用动态规划的思想,对于两个字符串,用dp[i][j]来表示同时以x_i和y_j结尾的子串,那么很明显当x_i不等于y_j时,dp[i][j]=0,当x_i等于y_j时候,dp[i][j] = dp[i - 1][j - 1] + 1,所以转移方程如下
![938cc5da1b39b4a68d62d694365c9794.png](https://i-blog.csdnimg.cn/blog_migrate/2c2558753b8fbd37b18705a7bbd755a7.jpeg)
那么代码也就很容易了,如下
int LS(string x, string y) { int maxLen = 0; int xlen = x.size(); int ylen = y.size(); for (int i = 0; i <= xlen; i++) { for (int j = 0; j <= ylen; j++) { if (i == 0 || j == 0) { dp[i][j] = 0; } else if (x[i - 1] == y[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = 0; } maxLen = max(maxLen, dp[i][j]); } } return maxLen;}
很明显,动态规划法两个循环,时间复杂度为O(mn),通常这种时间复杂度也就够用了。但其实还有其它更优的算法,例如基于后缀数组的最长公共子串求法。
![ff141e04ee79f46621031ad973fafe97.png](https://i-blog.csdnimg.cn/blog_migrate/452f31144cefebc4556ed41fae8188d5.jpeg)
三. 后缀数组法
这个算法稍微复杂一点,通过构造后缀数组,时间复杂度会更低。基于二倍增算法的后缀数组时间复杂度为O(n*log(n)),而基于DC3算法的后缀数组时间复杂度为O(n)。后面有时间我会详细讲解这两种算法。
可以看出通过计算公共子串的方法来计算两个串的相似度还存在缺陷,比如很多情况是两个字符串高度相似,但子串并不一定很长,后面我再讲解其它算法来逐步改进这个问题。下一节我会讲解更多关于字符串相似度计算算法,欢迎大家关注!