算法 后缀数组
来源:互联网 发布: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;}
- 后缀数组 da算法
- 算法 后缀数组
- 后缀数组 倍增算法
- 后缀数组,倍增算法
- poj1226(kmp算法,后缀数组)
- 倍增算法实现后缀数组
- hdu4691(后缀数组+ST算法)
- hdu4622(后缀数组+ST算法)
- 后缀数组之倍增算法
- 后缀数组倍增算法模版
- 后缀数组之倍增算法
- 后缀数组 倍增算法模板
- 后缀数组 倍增算法详解
- 倍增算法求解字符串的后缀数组
- 构造后缀数组的DC3算法实现
- 【*】后缀数组(dc3算法构造)
- 最长回文(后缀数组||Manacher算法)
- 后缀数组,Manber & Mayer 倍增算法
- U-boot 的编译链接与移植
- POJ 1087 A Plug for UNIX
- 按指定行数分割文件
- GCC 内嵌汇编输出
- iptables详解
- 算法 后缀数组
- Oracle SQL Developer连接报错(ORA-12505)的解决方案(两种)
- C# 计算变量大小
- ubuntu下编译FFmpeg
- NET Core 介绍
- hive与json:使用HDFS上的json格式数据建立hive表
- Android程序Crash时如何获取异常信息
- 运行时异常与一般异常的区别
- Spring Boot (十四)集成Dubbo