python实现后缀数组

刷蓝桥杯题目的时候碰到了后缀数组,就去学习了一下,虽然可能因为python问题还是没ac
题目传送门[蓝桥杯 2014 国 AC] 重复模式

后缀数组

后缀数组(SA) 是强大的处理字符串的算法,看网上都是c++的代码,这里给出一手python的代码

前置定义:

字串: 在字符串s中,任取i<=j,则在s中截取从i到j的这一段就叫做s的字串
后缀: 就是从字符串s的某个位置i到字符串末尾的字串,我们定义以s的第i个字符为第一个元素的后缀为suff(i)
后缀数组: 把s的每个后缀按照字典序排序,
后缀数组SA[i] 表示 排名为i的后缀的起始下标位置,
而它的映射数组rank[i] 表示 起始位置下标为i的后缀的排名
简单来说,SA表示排名为i的起始下标,rank表示起始下标为i的排名是几
在这里插入图片描述

后缀数组的具体实现

在暴力的情况下,快排 O ( n l o g n ) O(nlogn) O(nlogn)排序每个后缀,但是这是字符串,比较任意两个后缀的时间 O ( n ) O(n) O(n),合起来接近 O ( n 2 l o g n ) O(n^2logn) O(n2logn)的复杂度,我们采取2方面的优化

倍增
对于读入的字符串根据单个字符排序,相同的字符拥有同样的序号,这里称作关键字
在这里插入图片描述
接下来把相邻的关键字合并到一起,即每个字符以自己的关键字为第一关键字,以下一个字符的关键字为第二关键字,没有第二关键字的补零,按照新合并的关键字排完序后再次标号
在这里插入图片描述

然后再以第i个字符自己的关键字为第一关键字,第i+2个字符的关键字为第二关键字,重复上一步,再以第i+4为第二关键字倍增 … \dots 直至排序标号后所有的关键字都不一样

在这里插入图片描述
显然这样排序的速度稳定在 O ( l o g n ) O(logn) O(logn)
基数排序
如果用快排的化,复杂度是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
观察排序过程,每次都是排序两位数,所以基数排序能优化到 O ( n ) O(n) O(n)
显然后缀数组这东西不可能让你排个序就完事了

构造最长公共前缀——Height

定义:
Height[i]: 表示suff[SA[i]]和suff[SA[i - 1]]的最大公共前缀,也就是排序后相邻的两个后缀的最长公共前缀
而两个排名不相邻的最长公共前缀为排名在他们之间的Height的最小值
一般用h[i]表示Height[rank[i]]
在这里插入图片描述
利用后缀数组高效地得到Height
先给出一个结论h[i] ≥ \ge h[i - 1] - 1$$Height[rank[i]] ≥ \ge Height[rank[i - 1]]
这个性质很简单,大家花点时间自己想一下
根据这个性质,我们遍历一遍SA就可以得到Height了

代码

有点绕,慢慢理解下

def get_SA(s, n, m):
    MAXN = 500005
    rank = [0] * MAXN
    c = [0] * MAXN
    sa = [0] * MAXN
    y = [0] * MAXN
    Height = [0] * MAXN
    for i in range(1, n + 1):
        rank[i] = ord(s[i - 1])
        c[rank[i]] += 1
    for i in range(1, m):
        c[i] += c[i - 1]
    for i in range(n, 0, -1):
        sa[c[rank[i]]] = i
        c[rank[i]] -= 1
    k = 1
    while(k <= n):
        num = 0
        for i in range(n - k + 1, n + 1):
            num += 1
            y[num] = i
        for i in range(1, n + 1):
            if(sa[i] > k):
                num += 1
                y[num] = sa[i] - k
        for i in range(1, m + 1):
            c[i] = 0
        for i in range(1, n + 1):
            c[rank[i]] += 1
        for i in range(1, m + 1):
            c[i] += c[i - 1]
        for i in range(n, 0, -1):
            sa[c[rank[y[i]]]] = y[i]
            c[rank[y[i]]] -= 1
            y[i] = 0
        rank, y = y, rank
        rank[sa[1]] = 1
        num = 1
        for i in range(2, n + 1):
            if(y[sa[i]] == y[sa[i - 1]] and y[sa[i] + k] == y[sa[i - 1] + k]):
                rank[sa[i]] = num
            else:
                num += 1
                rank[sa[i]] = num
        if(num == n):
            break
        m = num
        k *= 2
    for i in range(1, n + 1):
        k = max(k - 1, 0) if k else k
        j = sa[rank[i] - 1]
        while(i + k <= n and j + k <= n and s[i + k - 1] == s[j + k - 1]):
            k += 1
        Height[rank[i]] = k
    ans = 1
    for i in range(2, n + 1):
        ans = max(Height[i], ans)
    print(ans)

if __name__ == '__main__':
    s = input()
    n = len(s)
    get_SA(s, n, 256)

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值