数据结构:后缀数组

后缀数组

资料:https://pan.quark.cn/s/43d906ddfa1b、https://pan.quark.cn/s/90ad8fba8347、https://pan.quark.cn/s/d9d72152d3cf

一、后缀数组的定义

后缀数组(Suffix Array,简称 SA)是一种针对字符串 的高效数据结构,它将字符串的所有后缀 按字典序排序 后,存储这些后缀的起始索引。

给定一个长度为 n 的字符串 S = s₀s₁...sₙ₋₁,其第 i 个后缀为 S[i:] = sᵢsᵢ₊₁...sₙ₋₁。后缀数组 sa 是一个长度为 n 的数组,满足 sa[k] = i 表示第 k 小的后缀是 S[i:] ,且字典序满足 S[sa[0]:] < S[sa[1]:] < ... < S[sa[n-1]:]。

辅助数组

为了高效处理后缀相关问题,通常会搭配两个辅助数组:

  1. 排名数组 rk :rk[i] = k 表示后缀 S[i:] 在排序后的后缀数组中排名为 k,与 sa 互为逆数组,即 sa[rk[i]] = i 且 rk[sa[k]] = k。
  2. 高度数组 height :height[k] 表示排名为 k 的后缀与排名为 k-1 的后缀的**最长公共前缀(LCP)**长度,即 height[k] = LCP(S[sa[k]:], S[sa[k-1]:]),规定 height[0] = 0。

二、后缀数组的核心特性

  1. 字典序有序性:后缀数组中的后缀按字典序升序排列,这是解决字符串匹配、重复子串等问题的基础。
  2. 排名与后缀的双向映射 :通过 sa 和 rk 可以快速查询后缀的排名,或排名对应的后缀起始索引。
  3. 最长公共前缀的传递性 :利用 height 数组可快速计算任意两个后缀的最长公共前缀长度:LCP(i,j) = min{height[rk[i]+1 ... rk[j]]}(假设 rk[i] < rk[j])。

三、后缀数组的构建算法

构建后缀数组的核心是对所有后缀进行高效排序,直接排序的时间复杂度为 O(n² log n)(比较两个后缀的时间为 O(n)),对于长字符串效率极低。因此需要更优的算法,常用的有:

1. 倍增算法(主流算法)

核心思想 :通过倍增长度的方式,逐步确定每个后缀的排名,避免直接比较长后缀。

  • 步骤 :
    1. 初始化 :先对每个字符(长度为 1 的子串)排序,得到初始的 sa 和 rk。
    2. 倍增排序 :对于长度 len = 2,4,8,...,将每个后缀的前 len 个字符拆分为前 len/2 字符 和后 len/2 字符 ,以 (rk[i], rk[i+len/2]) 为关键字进行排序,更新 sa 和 rk。
    3. 终止条件 :当 len ≥ n 时,所有后缀的排名已确定。
  • 时间复杂度 :O(n log n),实现简单且效率较高,是工程中常用的方法。

2. DC3 算法

核心思想 :基于基数排序的分治算法,将后缀分为三类进行排序,进一步优化时间复杂度。

  • 时间复杂度 :O(n),但实现复杂,适合对时间要求极高的场景。

四、后缀数组的实现示例(倍增算法)

python 复制代码
def build_sa(s):
    n = len(s)
    sa = list(range(n))
    rk = [ord(c) for c in s]  # 初始排名为字符的ASCII码
    tmp = [0] * n  # 临时数组,用于排序
    k = 1  # 倍增长度
    
    while k < n:
        # 排序关键字:(rk[i], rk[i+k]),i+k超出范围则为-1
        def cmp(i):
            return (rk[i], rk[i + k] if i + k < n else -1)
        
        # 对sa数组按新关键字排序
        sa.sort(key=cmp)
        
        # 更新tmp数组为新的排名
        tmp[sa[0]] = 0
        p = 0  # 排名计数器
        for i in range(1, n):
            # 若当前后缀与前一个后缀的关键字不同,排名+1
            if cmp(sa[i]) != cmp(sa[i-1]):
                p += 1
            tmp[sa[i]] = p
        
        # 更新rk数组
        rk[:] = tmp[:]
        k *= 2  # 倍增长度
    
    return sa, rk

def build_height(s, sa, rk):
    n = len(s)
    height = [0] * n
    k = 0  # 公共前缀长度
    for i in range(n):
        if rk[i] == 0:
            continue
        if k > 0:
            k -= 1
        j = sa[rk[i] - 1]  # 前一个排名的后缀起始索引
        # 扩展公共前缀长度
        while i + k < n and j + k < n and s[i + k] == s[j + k]:
            k += 1
        height[rk[i]] = k
    return height

使用示例

python 复制代码
s = "abracadabra"
n = len(s)
sa, rk = build_sa(s)
height = build_height(s, sa, rk)

print("字符串:", s)
print("后缀数组 sa:", sa)
print("排名数组 rk:", rk)
print("高度数组 height:", height)

# 输出解释:
# sa[0] = 10 表示排名0的后缀是 s[10:] = "a"
# rk[10] = 0 表示后缀 s[10:] 排名为0
# height[1] 表示排名1的后缀与排名0的后缀的最长公共前缀长度

五、后缀数组的时间复杂度

  • 构建(倍增算法) :O(n log n),其中排序的时间为 O(n log n),倍增的次数为 log n。
  • 高度数组构建 :O(n),利用公共前缀的传递性,避免重复比较。
  • 查询任意两后缀的 LCP :若搭配**区间最小值查询(RMQ)**预处理 height 数组,查询时间为 O(1),预处理时间为 O(n log n)。

六、后缀数组的典型应用

后缀数组是处理字符串问题的"万能工具",常用于以下场景:

  1. 字符串匹配 :在主串 S 中匹配模式串 P,可将 P 与 S 的后缀数组中的后缀进行二分查找,时间复杂度 O(|P| log |S|)。
  2. 最长重复子串 :字符串中出现至少两次的最长子串,其长度等于 height 数组的最大值。
  3. 最长公共子串 :给定两个字符串 S 和 T,拼接为 S + '#' + T 后构建后缀数组,找到分别来自 S 和 T 的后缀的最大 height 值。
  4. 不同子串计数 :字符串中不同子串的总数为 n(n+1)/2 - sum(height[1...n-1])(总子串数减去重复子串数)。
  5. 后缀排序与字典序相关问题:如求字符串的最小表示、按后缀字典序输出子串等。

七、后缀数组与其他字符串结构的对比

数据结构 核心优势 适用场景 时间复杂度(构建)
后缀数组 处理 LCP 问题高效,功能全面 重复子串、公共子串、匹配 O(n log n)
字典树(Trie) 前缀匹配高效 前缀查询、词频统计 O(n)
后缀自动机(SAM) 空间效率极高,支持动态添加 海量字符串的子串问题 O(n)

后缀数组的优势在于直观易懂 且功能全面 ,缺点是空间复杂度较高(需存储 sa、rk、height 三个数组),而后缀自动机在空间和时间上更优,但理解和实现难度更大。

相关推荐
星夜离尘561 小时前
MATLAB 因果采样保持:用历史匹配和数据年龄防止未来信息泄漏
数据结构·算法·matlab
思麟呀2 小时前
BTree的原理与 C++ 完整实现
c语言·数据结构·c++·算法·1024程序员节
黄俊懿2 小时前
【架构师从入门到进阶】第七章:缓存和存储的优化思路——第一节:缓存概述
数据结构·数据库·分布式·缓存·架构·系统架构·1024程序员节
晒太羊的猫3 小时前
hot100 堆
数据结构
啵啵啵鱼3 小时前
用 Claude Code 重构遗留系统:从评估到落地的完整实践
数据结构·1024程序员节
如何原谅奋力过但无声4 小时前
【LeetCode hot100】代码随想录未cover部分
数据结构·算法·leetcode
H.莓飛4 小时前
【数据结构】排序_OJ题(二):划分与选择
linux·服务器·c语言·开发语言·数据结构·visual studio
番茄味城市甜菜4 小时前
数据结构:单链表(C语言)
数据结构·链表·1024程序员节
蹊黎4 小时前
C++中stack、queue、priority queue类的部分简单实现
数据结构·c++·visualstudio·1024程序员节
春弦_14 小时前
自用算法整理二
数据结构·算法·图论