首页 > 技术文章 > 后缀数组

justPassBy 2015-06-27 18:44 原文

后缀数组是根据一个给定的字符串,然后取这个字符串的所有后缀,然后将后缀排序,生成两个数组,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] .....就可以了

推荐阅读