后缀数组 (SA)

约定

  • 本文中字符串下标从1开始
  • 后缀(i)表示(s[idots n])

简述

后缀数组(Suffix Array)主要由两个数组组成:(sa)(rank)
其中(sa_i)表示排名为(i)的后缀,(rank_i)表示后缀(i)的排名。其二者构成了双映射的关系,即:(sa_{rank_i}=i,rank_{sa_i}=i)
由于我们知道子串可以视作某个后缀的前缀,所以有时会在字符串的题目中利用此性质用到后缀数组。

(O(nlog^2n))解法

若一次比较的复杂度是(O(1))的,显然直接可以利用(sort)获得(O(nlogn))的排序复杂度。但对于两个字符串无法实现比较的(O(1))。回想字符串的比较方式,先找到两者的(lcp)(最长公共前缀),再比较下一位的大小即可(下一位为空视为最小)。也就是说,只要有较为快速的方法找到(lcp),就可以快速地进行比较。此时可以利用二分的方法,用哈希值判断是否相等,(O(log n))地找到两者的(lcp)并实现大小的比较。于是整体的排序复杂度即为(O(nlog^2n))
有可能遇上哈希冲突的问题,但此种方法胜在好写。

(O(nlogn))解法

这是一种基于倍增思想的做法。
先对每个长度为1的子串(即每个字符)进行排序。
当已知长度为(w)的所有子串排名情况时,对于长度为(2w)的所有子串,其比较就是一个双关键字的比较。即先比较长度为(w)的前缀,若相等再比较后缀。超出(n)的部分统一视为无穷小,不会影响原子串的大小判断。等到(w)足够大((ge n-1))时,由于(s[idots i+w])与后缀(i)的排名相同,最后得到的(sa_i)即为所求。
假设每次对长度为(w)的排序都可以实现(O(n))的时间复杂度,则总体便可以达到(O(nlogn))。因为在此背景下值域为排名,与(n)同阶,可以直接采用内层套用计数排序基数排序实现(O(n))的排序。

原文地址:https://www.cnblogs.com/hkr04/p/Suffix-Array.html