LCP数组的实现和最长公共连续子串

来源:互联网 发布:微博个性域名怎么删除 编辑:程序博客网 时间:2024/05/21 10:51

        LCP数组(Longest Common Prefix Array, 高度数组):是由后缀数组中相邻两个后缀的最长公共前缀的长度组成的数组。

        假设字符串S, 后缀数组sa, LCP数组lcp, 那么有后缀S[sa[i]...]与S[sa[i + 1]...]的最长公共前缀的长度为lcp[i]。

        lcp的计算: (后缀数组的实现)假设S[i...]与S[sa[rank[i]]...]的前h个字符的长度相等, 那么S[i+1...]与S[sa[rank[i + 1]]...]的前h - 1个字符也相等。 那么可以在O(n)的时间内求出lcp哦。

        lcp的简单应用:

        1)求一个字符串中出现次数至少两次的最长子串的长度。比如abracadabra,那么结果为4(abra出现两次且最长)。那么怎么利用lcp来求出答案呢?由后缀数组和lcp的性质可以知道,子串的开始位置在后缀数组中相距越远,其公共前缀的长度也就越短。找出lcp中最大的值,就是答案了。

        2)求两个字符串的最长公共连续子串。利用上面的结论,可以把两个字符串连接起来,然后对其求lcp即可。

S="abracadabra"的lcpisa[i]lcp[i]S[sa[i]...]0110'\0'(空)1101a274abra301abracadabra431acadabra550adabra683bra710bracadabra840cadabra960dabra1092ra112-racadabra

#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;#define MAX_N 10005#define max(a, b) (a > b ? a : b)char strA[MAX_N], strB[MAX_N], strC[MAX_N*2 + 5];int sufArr[MAX_N], lcp[MAX_N], rank[MAX_N], temp[MAX_N];int n, k;bool compareSufArr(int i, int j) {int ri, rj;if (rank[i] != rank[j]) {return rank[i] < rank[j];} else {ri = i + k <= n ? rank[i + k] : -1;rj = j + k <= n ? rank[j + k] : -1;return ri < rj;}}void constructSufArr(char* str, int* sa, int len) {int i;n = len;for (i = 0; i <= n; i++) {sa[i] = i;rank[i] = i < n ? str[i] : -1;}for (k = 1; k <= n; k *= 2) {sort(sa, sa + n + 1, compareSufArr);temp[sa[0]] = 0;for (i = 1; i <= n; i++) {temp[sa[i]] = temp[sa[i - 1]] + (compareSufArr(sa[i - 1], sa[i]) ? 1 : 0);}for (i = 0; i <= n; i++) {rank[i] = temp[i];}}}// 构造后缀数组的lcpvoid constructLcp(char* str, int* sa, int* cp, int len) {int i, j;int h;for (i = 0; i <= len; i++) {rank[sa[i]] = i;}h = 0;cp[0] = 0;for (i = 0; i < len; i++) {j = sa[rank[i] - 1];if (h > 0) {--h;}while (i + h < n && j + h < n && str[i + h] == str[j + h]) {++h;}cp[rank[i] - 1] = h;}} // O(n)// 求一个字符串中出现过的最长重复子串int lrs(char* str) {int len;int res;int i;len = strlen(str);constructSufArr(str, sufArr, len);constructLcp(str, sufArr, lcp, len);res = 0;for (i = 0; i < len; i++) {res = max(res, lcp[i]);}return res;}// 求两个字符串的最长公共连续子串int lcs(char* ms, char* ss) {int ml, sl, len;int res;int i;ml = strlen(ms), sl = strlen(ss);// strC = ms + '\0' + ssstrcpy(strC, ms);strC[ml + 1] = '\0';strcpy(strC + ml + 2, ss);// 对strC求后缀数组和lcplen = ml + sl + 2;constructSufArr(strC, sufArr, len);constructLcp(strC, sufArr, lcp, len);//res = 0;for (i = 0; i < len; i++) {// 需要分属不同的字符串,否则就变成了在同一个字符串中求最长公共连续子串的长度if ((sufArr[i] < ml) != (sufArr[i + 1] < ml)) {res = max(res, lcp[i]);}}return res;}int main() {while (scanf("%s%s", strA, strB) != EOF) {printf("%d %d\n", lrs(strA), lrs(strB));printf("%d\n", lcs(strA, strB));}return 0;}
qbvdvghj suffixarrayabracadabra suffixarraysuffixarray abracadabraabcdefg abcdefgxyzqu iopcye

原创粉丝点击