后缀数组

后缀数组是根据一个给定的字符串,然后取这个字符串的所有后缀,然后将后缀排序,生成两个数组,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\n", ans);

109     }

110     return 0;

111 }
View Code

 

根据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] .....就可以了

你可能感兴趣的:(后缀数组)