后缀数组之倍增算法

首先说明 :后缀数组的构建在网上有多种方法:朴素的n*n*logn,还有倍增n*logn的,还有3*n的DC3算法,当然还有DC算法。这个算法学习自林厚丛老师的《高级数据结构》,代码较长,而且常数也比较大,但是是我这种笨人可以理解的。如有人想学短而快的可以学习《罗穗骞 后缀数组 ---处理字符串的有力工具》。顺便说一下,罗大神的算法书写的的确很短小也漂亮,可惜我看不懂。

说一下学习的心路历程吧!最开始想学后缀树,道理看明的了,可是一看代码实在是太长了(可能是我找的模版不对吧)。后来看到后缀数组的功能也不错,可以实现后缀树的很多功能,于是转向后缀数组。于是向林大神学习,可是在他漂亮的代码映照下的是我愚笨的脑袋,最后是林厚丛老师救了我。感谢林老师!!!

学习前的准备:

1、后缀数组的各种基本概念

  后缀:字符串中从第i个开始到它的最后一个。如字符串abcde。则bcde、cde、de、e都是他的后缀,当然他本身也是自己的后缀。

  后缀数组:有两种sa数组和rank数组。

    sa[i]表示把字符串的所有后缀排序后排第i的是以第几个字母开头的后缀。

    rank[i]表示以第i个字母开头的后缀在后缀的排序中排第几。

2、计数排序和基数排序(可以百度一下)

  计数排序也就是桶排,时间复杂度O(n)

 1 #include <iostream>
 2 using namespace std;
 3 const int MAXN = 100000;
 4 const int k = 1000; // range
 5 int a[MAXN], c[MAXN], ranked[MAXN];
 6
 7 int main() {
 8     int n;
 9     cin >> n;
10     for (int i = 0; i < n; ++i) {
11         cin >> a[i];
12         ++c[a[i]];
13     }
14     for (int i = 1; i < k; ++i)
15         c[i] += c[i-1];
16     for (int i = n-1; i >= 0; --i)
17         ranked[--c[a[i]]] = a[i];//如果是i表达的是原数标号,a[i]就是排序后的正确序列
18     for (int i = 0; i < n; ++i)
19         cout << ranked[i] << endl;
20     return 0;
21 }

  基数排序,也称桶子排序(注意和上面的区分),实际上是分关键安排序。首先按次关键字排,再按首关键字排。

 1 int maxbit(int data[], int n) //辅助函数,求数据的最大位数
 2 {
 3     int d = 1; //保存最大的位数
 4     int p = 10;
 5     for(int i = 0; i < n; ++i)
 6     {
 7         while(data[i] >= p)
 8         {
 9             p *= 10;
10             ++d;
11         }
12     }
13     return d;
14 }
15 void radixsort(int data[], int n) //基数排序
16 {
17     int d = maxbit(data, n);
18     int *tmp = newint[n];
19     int *count = newint[10]; //计数器
20     int i, j, k;
21     int radix = 1;
22     for(i = 1; i <= d; i++) //进行d次排序
23     {
24         for(j = 0; j < 10; j++)
25             count[j] = 0; //每次分配前清空计数器
26         for(j = 0; j < n; j++)
27         {
28             k = (data[j] / radix) % 10; //统计每个桶中的记录数
29             count[k]++;
30         }
31         for(j = 1; j < 10; j++)
32             count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
33         for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中
34         {
35             k = (data[j] / radix) % 10;
36             tmp[count[k] - 1] = data[j];
37             count[k]--;
38         }
39         for(j = 0; j < n; j++) //将临时数组的内容复制到data中
40             data[j] = tmp[j];
41         radix = radix * 10;
42     }
43     delete[]tmp;
44     delete[]count;
45 }

3、后缀数组倍增算法的基本思想

  一个字符串的所有后缀就是n(字符串的长度)个字符串,如果对它们进行排序就是n*n,由于字符串的比较要扫描串长所以时间复杂度也就成了n*n*n,如果用快排的思想n*n*logn。

  倍增算法的思想:

    首先用计数排序的方法对单个字符进行排序,得到按单字符进行排序后的rank[],以后的排序就是以此数组代表字符进行排序。

      aabaaaaba

      112111121(rank[])

    单个字符很有可能是有重复的,所以要比较第二个字符。但是第二个字符的大小已经比较过了(最后一个字符开始的串没有第二个字符,所以补0)。即

         abaaaaba0

         121111210

    这样就以第二个字符的大小为第二关键字,第一个字符的大小为第一关键字进行基数排序。得到以两个字符进行排序后的rank[]。

    同样,我们可以用后面已经算好的两个字符的大小算出按4个字符排序的顺序。然后是8个、16个……。直到字符串的长度。

代码:

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<cstring>
 4 #include<algorithm>
 5
 6 using namespace std;
 7 int s[100],rank[100],sa[100];//ss:字符串,s:ss对应的数值,rank:rank数组,sa:sa数组
 8 char ss[100];
 9 void build(int *st,int *sa,int *rank,int n,int mx)
10 {
11     int *cnt=new int[mx+3],*cntrank=new int[n+3];//cnt:各个字符出现的次数
12     int *rank1=new int[n+3],*rank2=new int[n+3];//关键字
13     int *tpsa=new int[n+3];                        //临时sa
14     memset(cnt,0,sizeof(int)*(mx+3));
15     for(int i=0;i<n;i++)cnt[st[i]]++;            //计每个字符出现的次数
16     for(int i=1;i<=mx;i++)cnt[i]+=cnt[i-1];        //第i个字符的名次范围
17     for(int i=0;i<n;i++)rank[i]=cnt[st[i]]-1;    //第i个字符的排名,到这里完成单个字符的计数排序
18
19     for(int l=1;l<n;l<<=1)        //进行倍增
20     {
21         for(int i=0;i<n;i++)    //取得第一、第二关键字
22         {
23             rank1[i]=rank[i];
24             rank2[i]=i+l<n?rank[i+l]:0;
25         }
26         memset(cntrank,0,sizeof(int)*(n+3));        //按第二关键字进行计数排序,基数排序的第一步
27         for(int i=0;i<n;i++)cntrank[rank2[i]]++;    //统计排名重复的次数
28         for(int i=1;i<n;i++)cntrank[i]+=cntrank[i-1];    //统计次数累加
29         for(int i=n-1;i>=0;i--)    tpsa[--cntrank[rank2[i]]]=i;    //tpsa[第i个字符开头的字符串的第二关键字的名次]=i
30         memset(cntrank,0,sizeof(int)*(n+3));
31         for(int i=0;i<n;i++)cntrank[rank1[i]]++;
32         for(int i=1;i<n;i++)cntrank[i]+=cntrank[i-1];
33         for(int i=n-1;i>=0;i--)sa[--cntrank[rank1[tpsa[i]]]]=tpsa[i];    //sa[第二关键字排名第i的字符串的第一关键字(排名数累加--即为)排名]=第二关键字排名第i的字符串
34         rank[sa[0]]=0;
35         for(int i=1;i<n;i++)    // 除第0个外,如果排名第i的字符串的第一二关键字与第i-1个的相同则排名也要相同。
36         {
37             rank[sa[i]]=rank[sa[i-1]];
38             if(!(rank1[sa[i]]==rank1[sa[i-1]]&&rank2[sa[i]]==rank2[sa[i-1]]))rank[sa[i]]++;
39         }
40     }
41     delete []cnt;delete []cntrank;delete []rank1;delete []rank2;delete []tpsa;
42 }
43 int main()
44 {
45     scanf("%s",ss);
46     for(int i=0;ss[i];i++)s[i]=ss[i];
47     build(s,sa,rank,strlen(ss),255);
48     for(int i=0;i<strlen(ss);i++)cout<<sa[i]<<" ";
49     cout<<endl;
50     for(int i=0;i<strlen(ss);i++)cout<<rank[i]<<" ";
51     return 0;
52 }

时间: 2024-11-01 20:24:24

后缀数组之倍增算法的相关文章

后缀数组的倍增算法模板

代码摘自2009年国家集训队论文 1 #include<iostream> 2 #define ws gzh 3 //ws是库中定义过的名称,有些编译器无法通过 4 using namespace std; 5 const int maxn=1024; 6 int wa[maxn],wb[maxn],wv[maxn],ws[maxn]; 7 int cmp(int *r,int a,int b,int l) 8 {return r[a]==r[b]&&r[a+l]==r[b+l

后缀数组:倍增法和DC3的简单理解

一些定义:设字符串S的长度为n,S[0~n-1]. 子串:设0<=i<=j<=n-1,那么由S的第i到第j个字符组成的串为它的子串S[i,j]. 后缀:设0<=i<=n-1,那么子串S[i,n-1]称作它的后缀,用Suffix[i]表示. 串比较:对于两个串S1,S2,设长度分别为n1,n2.若存在一个位置i,使得对于0<=j<i满足S1[j]=S2[j]且S1[i]<S2[i],那么我们称S1<S2.如果S1是S2的一个前缀,那么也有S1<S2

●后缀数组○十三个例题

●之前学习过后缀数组的倍增算法,但也只是简单练了练倍增(O(n ㏒ n)). ●如今再次开始后缀,借助罗穗骞的论文<后缀数组--处理字符串的有力工具>,练习了论文里那十三个例题,学习了里面所包含的后缀数组处理字符串的应用. ●感觉收获不少,后缀倍增+后缀应用的代码能力提高了不少. ●于此发上各题方法的小总结和以及的AC代码 ●后缀数组的应用 ●一个字符串 ○求字符串的字串个数.(spoj694 spoj705) 方法:一个串中不同子串的总数=∑(len-height[i]-sa[i])

【后缀数组】【真难懂啊】

基本上一搜后缀数组网上的模板都是<后缀数组——处理字符串的有力工具>这一篇的注释,O(nlogn)的复杂度确实很强大,但对于初次接触(比如窝)的人来说理解起来也着实有些困难(比如窝就活活好了两天的光阴..),看了那么多材料感觉<挑战程序设计>的后缀数组解释理解起来会相对容易很多,然而它的复杂度是O(nlog2n)的,主要区别是对子串排序的时候前者用了基数排序--O(n),而后者用了快排--O(nlogn),这就导致了最终的复杂度后者比前者多了一个O(logn). 先整理下<挑

[bzoj1717][Usaco2006 Dec]Milk Patterns 产奶的模式 (hash构造后缀数组,二分答案)

以后似乎终于不用去学后缀数组的倍增搞法||DC3等blablaSXBK的方法了= = 定义(来自关于后缀数组的那篇国家集训队论文..) 后缀数组:后缀数组SA是一个一维数组,它保存1..n的某个排列SA[1],SA[2],……,SA[n],并且保证Suffix(SA[i])<Suffix(SA[i+1]),1≤i<n. 也就是将S的n个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入SA中. height数组:定义height[i]=suffix(sa[i-1])和suffix(sa[

利用后缀数组(suffix array)求最长公共子串(longest common substring)

摘要:本文讨论了最长公共子串的的相关算法的时间复杂度,然后在后缀数组的基础上提出了一个时间复杂度为o(n^2*logn),空间复杂度为o(n)的算法.该算法虽然不及动态规划和后缀树算法的复杂度低,但其重要的优势在于可以编码简单,代码易于理解,适合快速实现. 首先,来说明一下,LCS通常指的是公共最长子序列(Longest Common Subsequence,名称来源参见<算法导论>原书第3版p223),而不是公共最长子串(也称为最长公共子串). 最长公共子串问题是在文本串.模式串中寻找共有的

「kuangbin带你飞」专题十八 后缀数组

layout: post title: 「kuangbin带你飞」专题十八 后缀数组 author: "luowentaoaa" catalog: true tags: - kuangbin - 字符串 - 后缀数组 传送门 倍增法 struct DA{ bool cmp(int *r,int a,int b,int l){ return r[a]==r[b]&&r[a+l]==r[b+l]; } int t1[maxn],t2[maxn],c[maxn]; int r

后缀数组,集各家之大成

作用有待更新.... 大佬博客:https://www.cnblogs.com/jinkun113/p/4743694.html: https://www.luogu.com.cn/problemnew/solution/P3809: 后缀数组: 复杂度:N(NlogN) 理论原理加模板: 后缀数组使用倍增法这里不加以叙述+桶排序: 先来了解一下桶排序,要拍一个1-99的数,我们先按照各位放到十个桶里,然后按照顺序取出来放到十位的桶里,这样我们在保证十位相同的时候个位小的先被放入桶里,所以答案的

用倍增法构造后缀数组中的SA及RANK数组

感觉后缀数组很难学的说= = 不过总算是啃下来了 首先 我们需要理解一下倍增法构造的原理 设原串的长度为n 对于每个子串 我们将它用'\0'补成长度为2^k的串(2^k-1<n<=2^k) 比如串aba的子串就有 aba'\0'    ba'\0''\0'  a'\0''\0''\0' 每次操作我们可以排出所有长度为 2^x的子串的大小 比如串aba的排序过程 第一遍 a                   a             b 第二遍 a'\0'             ab