后缀数组是根据一个给定的字符串,然后取这个字符串的所有后缀,然后将后缀排序,生成两个数组,sa数组和rank数组
sa[i]存的是排名第i的字符串下标
rank[i]存的是以下标i开头的后缀的排名
所以sa[rank[i]] = i rank[sa[i]] = i
由于字符串的比较是多关键字比较,如果用sort排序的话,时间复杂度是O(n*n*logn),这太不实用了
如何高效的求出sa数组呢,可以用倍增算法来求,时间复杂度是O(n*logn)
这个论文写得很好了
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <algorithm> 5 #include <iostream> 6 #include <queue> 7 #include <stack> 8 #include <vector> 9 #include <map> 10 #include <set> 11 #include <string> 12 13 #pragma warning(disable:4996) 14 typedef long long LL; 15 const int INF = 1<<30; 16 /* 17 后缀是指从某个位置开始到字符串某位的 字符串 18 suffix(i)表示 str[i-->len(str)-1]; 19 sa[i]表示所有的后缀中字典序第i大的字符串下标 20 rank[i] 存的是 suffix(i) 在所有后缀中的排名 21 22 23 */ 24 const int N = 100000*2 + 10; 25 int rank[N], height[N], count[N], bucket[N], tmp[N]; 26 void buildSa(int *r, int *sa, int n, int m) 27 { 28 int *x=rank, *y = tmp, *t, p; 29 for (int i = 0; i < m; ++i) count[i] = 0; 30 for (int i = 0; i < n; ++i) count[x[i] = r[i]]++; 31 //这里的x是长度为字符串长度为1的rank数组,, 因为长度为1,所以排名就是该字符的大小 32 for (int i = 1; i < m; ++i) count[i] += count[i - 1]; 33 for (int i = n - 1; i >= 0; --i) sa[--count[x[i]]] = i; 34 35 for (int k = 1; k <= n; k <<= 1) 36 { 37 p = 0; 38 39 //存的是第二关键字的sa数组 40 //这些的第二关键字都是$,所以排名排在前面 41 for (int i = n - k; i < n; ++i) y[p++] = i; 42 //当前的字符串长度为k,所以下标大于k,才是第二关键字 y[] 存的是按第二关键字排序的字符串下标, sa[]本身就是有序的, 所以可以直接遍历 43 for (int i = 0; i < n; ++i) if (sa[i] >= k)y[p++] = sa[i] - k; 44 45 //y[i] 排名第i的字符串下标 (按第二关键字进行排序的) 46 // x[y[i]] 是排名第i的字符串的第一关键字的大小 bucket收集第一关键字的大小 47 for (int i = 0; i < n; ++i) bucket[i] = x[y[i]]; 48 49 //对第一关键字进行计数排序 50 for (int i = 0; i < m; ++i) count[i] = 0; 51 for (int i = 0; i < n; ++i) count[bucket[i]]++; 52 for (int i = 1; i < m; ++i) count[i] += count[i - 1]; 53 //算出y[i] 的第一关键字大小 是排在多少 54 for (int i = n - 1; i >= 0; --i) sa[--count[bucket[i]]] = y[i]; 55 56 57 //根据sa 算出rank ,, 如果两个排名相近的两个字符串, 第一二关键字都相同, 则排名相同 58 t = x; x = y; y = t; 59 p = 1; x[sa[0]] = 0; 60 for (int i = 1; i < n; ++i) 61 x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k] ? p - 1 : p++; 62 if (p >= n) break; 63 m = p; 64 } 65 } 66 void makeHeight(int *r, int *sa, int n) 67 { 68 int i, j, k = 0; 69 for (i = 1; i <= n; ++i) rank[sa[i]] = i; 70 for (i = 0; i < n; height[rank[i++]]=k) 71 for (k ? k-- : 0, j = sa[rank[i] - 1]; r[i + k] == r[j + k]; k++); 72 } 73 int r[N], sa[N]; 74 char str[N]; 75 int main() 76 { 77 int n1, n2, n3; 78 79 while (scanf("%s", str) != EOF) 80 { 81 n1 = strlen(str); 82 for (int i = 0; i < n1; ++i) 83 r[i] = str[i] - 'a' + 2; 84 r[n1++] = 1; 85 n3 = n1; 86 scanf("%s", str); 87 n2 = strlen(str); 88 for (int i = 0; i < n2; ++i) 89 r[n3++] = str[i] - 'a' + 2; 90 r[n3++] = 0; 91 buildSa(r, sa, n3, 28); 92 for (int i = 0; i < n3; ++i) 93 printf("%d ", sa[i]); 94 puts(""); 95 makeHeight(r, sa, n3 - 1); 96 int ans = -1; 97 for (int i = 1; i < n3; ++i) 98 { 99 100 if (height[i]>ans) 101 { 102 if (sa[i] < n1 && sa[i - 1] >= n1) 103 ans = height[i]; 104 else if (sa[i - 1] < n1 && sa[i] >= n1) 105 ans = height[i]; 106 } 107 } 108 printf("%d ", ans); 109 } 110 return 0; 111 }
根据sa数组和rank数组,我们可以求出height数组
height[i]表示排名第i的字符串和排名第i-1的字符串的最长前缀(LCP), 如果我们按顺序求 height[0] , height[1],height[2]...height[n] 那么时间复杂度是O(n*n)
这是因为这样子算时,字符串的起始下标可能是不相邻的,所以没有充分利用信息
我们可以定义一个辅助数组h[i] = height[rank[i]], 那么h[i] >= h[i-1] - 1
证明: 设 suffix(k) 是排在suffix(i-1)前一名的字符串,则他们的最长公共前缀是h[i-1], 那么suffix(k+1) 肯定排在字符串 suffix(i)的前面, 且它们的最长公共前缀至少为h[i-1]-1
因为suffix(k+1) 和 suffix(i) 相当于字符串suffix(k) 和suffix(i-1) 将第一个字符给去掉, 那它们的最长公共前缀不就是至少为h[i-1]-1
又suffix(k+1) 肯定排在字符串 suffix(i)的前面, 但是可能是前几名, 如果 suffix(k+1) < suffix(j) < suffix(i) 呢 , 那suffix(j) 于 suffix(i)的最长公共前缀也至少为h[i-1]-1
这是因为 如果suffix(k+1) 于suffix(i)前m个字符相等, 那么 suffix(k+1)与suffix(j)的前m个字符也相等,suffix(j)与suffix(i)的前m个字符也相等,但是第m+1个字符一定大于等于suffix(k+1)的第
m+1个字符,且小于等于suffix(i)的第m+1个字符串, 这样子suffix(j)才会排在suffix(k+1)和suffix(i)中间
所以只要按顺序求h[0] , h[1], h[2] .....就可以了