算法 后缀数组

来源:互联网 发布:mysql修改字符集utf8 编辑:程序博客网 时间:2024/06/04 19:31

考完了NOIP,虽然D2脑子进水,然而还是目测水到了一等奖,避免了GG。也是时候开启一些新的算法了。思来想去,还是搞一下后缀数组吧。

先简单说明后缀数组,是啥。字符串后缀知道吧。数组知道吧。后缀数组就是在将一个字符串的所有后缀按照常见的字典序排法,排一下。显然我们可以用stdsort来进行非常暴力的排序,然而复杂度为n^2logn,这并不优美。我们需要找到一种更高效的算法,有两种,一种是倍增,一种是Dc3,后者为线性复杂度,然而常数较大,并且难以理解,代码较长,所以先来研究一下第一种。

我们先来明确一下算法流程,先开一下下图

我们先根据原来的字符串,排序出每一位是第几大。

我们的算法叫倍增,怎么个倍增,我们是倍增每次比较字符串的长度。

然后我们设比较的长度为k

我们每次把i和i+k合并到一起,最后将新得到的数组进行排序,反复多次,知道数组内的每个排名都不相同就可以退出了。

然后我们如果在这用暴力的stl sort的话,显然复杂度不够优美,所以我们需要用到基数排序

并且基数排序还有一个优美的性质,就是在排序关键字相等时,不更改在原序列中的相对位置前后。

    for (int i = 0;i < m;i++) c[i] = 0;    for (int i = 0;i < n;i++) c[x[i] = r[i]]++;    for (int i = 1;i < m;i++) c[i] += c[i - 1];    for (int i = n - 1;i >= 0;i--) sa[--c[x[i]]] = i;

我们看一下上述代码,首先我们用c记录出每个元素的个数。

然后我们将c数组做前缀和处理。

然后我们这样子来愉快的排好序,至于这么做为什么是对的,读者可以自己模拟思考,在这解释下,sa i 表示长度排第i的后缀是从原字符串的哪一位开始。

这样子我们只要在倍增的过程中,处理出第二关键字,并按照这个来进行排序,就可以优美的求出后缀数组了。

上面讲的肯定会有些不足,可以参考下下面的代码,我会给予详细的注释,读者可以自行思考。

void sa_init(){    int p,*x = t1,*y = t2;    //这里用两个指针来方便交换数组,参考下面swap    //先根据最初的,每一个字符进行基数排序    for (int i = 0;i < m;i++) c[i] = 0;    for (int i = 0;i < n;i++) c[x[i] = r[i]]++;    for (int i = 1;i < m;i++) c[i] += c[i - 1];    for (int i = n - 1;i >= 0;i--) sa[--c[x[i]]] = i;    for (int k = 1;k <= n;k <<= 1)//我们在进行倍增    {        //利用之前的排序结果直接根据第二关键字进行排序        p = 0;        for (int i = n - k;i < n;i++) y[p++] = i;        //显然n-k 到 n-1 并没有第二关键字 因为i + k已经超出范围了。        for (int i = 0;i < n;i++) if (sa[i] >= k) y[p++] = sa[i] - k;        //显然我们sa[i]是有序的。那我我么i是sa[i] - k的第二关键字。所以我们将y数组的第p位记录sa[i]的第一关键字在x中的下表。        //现在我们愉快的按照第二关键字,进行了排序。        //然后我们再根据第一关键字进行排序,由于基数排序的特性,我们保持了在第一关键字有序的情况下,第二关键字有序,并排出了sa数组。        for (int i = 0;i < m;i++) c[i] = 0;        for (int i = 0;i < n;i++) c[x[y[i]]]++;        for (int i = 1;i < m;i++) c[i] += c[i - 1];        for (int i = n - 1;i >= 0;i--) sa[--c[x[y[i]]]] = y[i];        //        swap(x,y);        //我们下然下次的x数组是这次的y,所以进行交换。        p = 1;        x[sa[0]] = 0;//提前处理边界,这个显然。        //下面是根据sa数组更新x数组内的排名。此时的y记录的就是原先x的排好的。        for (int i = 1;i < n;i++) x[sa[i]] = y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k] ? p - 1 : p++;        if (p >= n) break;//如果凑够了n个,显然我们的排名已经不存在重复了。        m = p;//更新下次的基数排序的范围,因为我们基数排序拍的就是排名。    }}

有一句话,后缀数组求出来没有用,关键是还要求出heigt数组,height数组表示什么呢,是字典序排序相邻的两个后缀的最大公共长度。

我们为什么要搞相邻的两位,因为他们的字典序最相近,可能的公共前缀也最长。

我们此处定义一下,height[i]表示sa[i]和sa[i + 1]的最长公共前缀长度。

我们先考虑暴力的求法,显然是n^2的做法,我们暴力的扫描这个sa数组,并且暴力的去判断相邻的字符串,但我们想一下,我们现在已经暴力的判断了最长的后缀和他的下一个后缀,然后我们求出他们的最长公共前缀长k,那么我们考虑第二长的后缀和他在sa数组的下一个后缀的最长前缀。然而我感觉我将不清楚,于是来一张自己画得图

最后给出详细的代码:

uoj有模板题~

#include <cstdio>#include <cstring>#include <cmath>#include <algorithm>using namespace std;const int MAXN = 200000;int n,m = 30;char s[MAXN];int c[MAXN],t1[MAXN],t2[MAXN],sa[MAXN],r[MAXN],height[MAXN],rank[MAXN];void sa_init(){    int p,*x = t1,*y = t2;    //这里用两个指针来方便交换数组,参考下面swap    //先根据最初的,每一个字符进行基数排序    for (int i = 0;i < m;i++) c[i] = 0;    for (int i = 0;i < n;i++) c[x[i] = r[i]]++;    for (int i = 1;i < m;i++) c[i] += c[i - 1];    for (int i = n - 1;i >= 0;i--) sa[--c[x[i]]] = i;    for (int k = 1;k <= n;k <<= 1)//我们在进行倍增    {        //利用之前的排序结果直接根据第二关键字进行排序        p = 0;        for (int i = n - k;i < n;i++) y[p++] = i;        //显然n-k 到 n-1 并没有第二关键字 因为i + k已经超出范围了。        for (int i = 0;i < n;i++) if (sa[i] >= k) y[p++] = sa[i] - k;        //显然我们sa[i]是有序的。那我我么i是sa[i] - k的第二关键字。所以我们将y数组的第p位记录sa[i]的第一关键字在x中的下表。        //现在我们愉快的按照第二关键字,进行了排序。        //然后我们再根据第一关键字进行排序,由于基数排序的特性,我们保持了在第一关键字有序的情况下,第二关键字有序,并排出了sa数组。        for (int i = 0;i < m;i++) c[i] = 0;        for (int i = 0;i < n;i++) c[x[y[i]]]++;        for (int i = 1;i < m;i++) c[i] += c[i - 1];        for (int i = n - 1;i >= 0;i--) sa[--c[x[y[i]]]] = y[i];        //        swap(x,y);        //我们下然下次的x数组是这次的y,所以进行交换。        p = 1;        x[sa[0]] = 0;//提前处理边界,这个显然。        //下面是根据sa数组更新x数组内的排名。此时的y记录的就是原先x的排好的。        for (int i = 1;i < n;i++) x[sa[i]] = y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k] ? p - 1 : p++;        if (p >= n) break;//如果凑够了n个,显然我们的排名已经不存在重复了。        m = p;//更新下次的基数排序的范围,因为我们基数排序拍的就是排名。    }}void rank_init(){    for (int i = 0;i < n;i++) rank[sa[i]] = i;}int bl(int a, int b){    int ans = 0;    while(r[a++] == r[b++]) ++ans;    return ans;}void getht(){    int cur = 0;    for(int i = 0; i < n; i++)     {         if(cur) --cur;        height[rank[i]] = cur = cur+bl(i+cur, sa[rank[i]+1]+cur);    }}int main(){    scanf("%s",s);    n = strlen(s);    for (int i = 0;i < n;i++) r[i] = s[i] - 'a' + 2;    r[n] = 0;    n++;    sa_init();    rank_init();    n--;    getht();    for (int i = 1;i <= n;i++) printf("%d ",sa[i] + 1);    printf("\n");    for (int i = 1;i < n;i++) printf("%d ",height[i]);    printf("\n");    return 0;}


0 0
原创粉丝点击