zoukankan      html  css  js  c++  java
  • 后缀数组 SA

    考虑一个问题:

    给出一个长度为 (n) 的字符串,容易知道其一共有 (n) 个后缀,现将这 (n) 个后缀进行排序,求出排序后的后缀序列。

    这里给出一些定义:

    子串:字符串 (s) 中连续的一段字符,从第 (i) 个字符到第 (j) 个字符的子串记作 (s[idots j]) ,其表示的字符串为 (s_i,s_{i+1},s_{i+2},dots,s_{j-1},s_j) ,长度为 (j-i+1)
    后缀:指右端点与原串右端点重合的子串,子串 (s[idots n]) 记作 ( ext{后缀 i})
    关于两个字符串的大小,我们从两个字符串的第一个字符开始逐个比较,设当前比较到第 (i) 个字符。若 (s1[i]>s2[i]) ,则 (s1>s2) ;若当其中较短串全都比较完毕并无不同,那么看做较长的字符串较大。如若长度全都相等,则视为两个字符串相等。

    考虑将后缀进行排序的问题,显然各个字符串的长度都不相同,可知每个后缀都有唯一切确定的排名。

    朴素做法:

    对于朴素做法,我们可以使用快速排序 (left(sort ight)) 来进行处理,对于两个字符串的比较,我们进行 (mathcal{O}left(n ight)) 复杂度的比较,可以得到一个 (mathcal{O}left(n^2logn ight)) 的算法。

    不用说,这个复杂度显然不够优秀。

    乱搞做法

    我们可以想办法优化一下字符串比较,实际上对于两个字符串,我们可以将他们分为两部分,分别是前一段的相同区域以及后一段的不同区域。我们可以二分找出两个区域的分界处,使用哈希处理即可,复杂度是 (mathcal{O}left(nlog^2n ight)) 的。

    倍增做法

    我们使用倍增的方法比较字符串的大小,并使用基数排序的方法,最后可以达到一个比较优秀的复杂度 (mathcal{O}left(nlogn ight))

    我们的主体思路是我们将从每一个字符开始长度为 (2^k) 的字符串进行排序,枚举 (k) 将字符串长度进行倍增,最后得到所有后缀的排序。

    我们考虑基数排序,每个元素有两个关键字,并且关键字的值域并不大,可以使用桶来处理。我们首先枚举第二关键字,再枚举第一关键字,然后我们惊奇的发现排序已经完成了,算法的复杂度是 (mathcal{O}left(n ight)) 的。

    基数排序动图演示

    对于每次排序的两个关键字,都可以从上一次的排序结果里获取。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    const int N=1e6+7;
    char s[N];
    int n,rk[N],tp[N],m;
    
    int buc[N],sa[N],h[N];
    
    void Qsort()
    {
    	for(int i = 0;i <= m;i ++) buc[i] = 0;
    	for(int i = 1;i <= n;i ++) buc[rk[i]] ++;
    	for(int i = 1;i <= m;i ++) buc[i] += buc[i-1];
    	for(int i = n;i >= 1;i --) 
    		sa[buc[rk[tp[i]]]--] = tp[i];
    }
    
    int main()
    {
    	scanf("%s",s+1);n = strlen(s+1);
    	for(int i = 1;i <= n;i ++) rk[i] = s[i]-'a'+1,tp[i] = i;
    	m = 26;Qsort();m = 0;
    	for(int i = 1;i <= n;i ++) rk[sa[i]] = s[sa[i]] == s[sa[i-1]]?m:++m;
    	for(int ws = 1,p = 0;m < n;m = p,ws <<= 1,p = 0) {
    		for(int i = n-ws+1;i <= n;i ++) tp[++p] = i;
    		for(int i = 1;i <= n;i ++) if(sa[i] > ws) tp[++p] = sa[i]-ws;
    		Qsort();p = 0;swap(tp,rk);
    		for(int i = 1;i <= n;i ++) {
    			rk[sa[i]] = tp[sa[i]] == tp[sa[i-1]] && tp[sa[i]+ws] == tp[sa[i-1]+ws] ? p:++p;
    		}
    	}
    	for(int i = 1;i <= n;i ++) printf("%d ",sa[i]);
    	puts("");
    	for(int i = 1,p = 0;i <= n;h[rk[i++]] = p) {
    		for(p?--p:p;s[i + p] == s[sa[rk[i]-1] + p]; ++p);
    	}
    	for(int i = 2;i <= n;i ++) printf("%d ",h[i]);
    	return 0;
    }
    
  • 相关阅读:
    Go语言web开发---Beego的cookie
    Go语言web开发---Beego路由
    Go语言web开发---Beego基础
    Go语言协程并发---原子操作
    Go语言协程并发---条件变量案例《城管来啦》
    Go语言协程并发---条件变量
    Go语言协程并发---读写锁sync.RWMutex
    Go语言协程并发---互斥锁sync.Mutex
    [Python] 协程学习过程
    [爬坑] termux ssh 设置总是 permission denied
  • 原文地址:https://www.cnblogs.com/nao-nao/p/14405810.html
Copyright © 2011-2022 走看看