【倍增 桶排序】后缀数组

前言

C++算法与数据结构本博文代码打包下载

本博文代码打包下载

后缀数组

字符串s,长度为N,ranki记录si...N-1在所有后缀的字典序,i ∈ \in ∈0,N-1。比如:s = "bac",则rank = {2,1,3}。s = "abbc",则rank = {1,2,3,4}。sai = x,表示sx...n-1的字典序是i。 暴力做法,时间复杂度:O(NNlogn)。

si...N-1 简记为suff(i)。

倍增优化

sbilen 记录si...i+len-1的字典序。长短不足补'\0'。
性质一 :如果sbi1len <sbi2len。则sbi1len +len < sbi2 len2 + len2
性质二 :如果sbi1len == sbi2len。如N-i1 >len 且N-i2>len,则sbi1len+len和sbi2len+len的大小关系和sbi1+lenlen和sbi2+lenlen相同。
性质三 :如果sbi1len == sbi2len。不失一般性,N-i1 == len,N -i2 > len。则 sbi1len+len <sbi2len+len
l e n ≥ N len \ge N len≥N时,sbilen便是答案。

实现

CData有两个元素:一,iCmp当前一轮的排序依据。二,下标inx,si...len-1的i。

排序 l o g 2 N log_2N log2N轮,每轮时间复杂度:O(NlogN)。

初始:iCmp等于sinx。v记录所有CData。

每轮:

对v排序。

auto v1 = v

v10.iCmp=1,i=2 To N-1 v1i = v1i-1 + (vi1.iCmp != vi1-1.iCmp);

i = 0 < N

v1i.iCmp = v1i.iCmp*(N+1)+ x。

如果x < i+len, x = v1i+len.iCmp;否则为0 。
时间复杂度:O(NlogNlogN)

桶排序优化

按v升序将sainx+len放到sainx中。 v由于已经是升序,故各桶内部无需排序。每轮时间复杂度:O(N),总时间复杂度:O(NlogN)。

DC

人言:DC算法可以将时间复杂度降低到O(n),以后再学习。

典型应用:最长重复子串

就是所有后缀的最长公共前缀。

lcp(i,j) 是suff(rank(i))和suff(rank(j))的最长公共前缀。
性质四 : i ≤ j ≤ k i \le j \le k i≤j≤k,如果lcp(i,k)是len,lcp(j,k) ≥ \ge ≥len。否则,令第x个字符不同,如果前者的第x个字符小于后者的第x个字符,则前者的字典序应该小于i;如果前者的第x字符大于后者的第x个字符,则后者的字典序大于k。
推论一 : i ≤ j 1 ≤ j 2 ≤ k i \le j1 \le j2 \le k i≤j1≤j2≤k lcp(j1,j2) ≥ \ge ≥ lcp(i,k)。
推论二 (LCP Lemma): i < j < k i < j < k i<j<k, l c p ( i , k ) = m i n ( l c p ( i , j ) , l c p ( j , k ) ) lcp(i,k)=min(lcp(i,j),lcp(j,k)) lcp(i,k)=min(lcp(i,j),lcp(j,k))。根据推论一: m i n ( l c p ( i , j ) , l c p ( j , k ) ) ≥ l c p ( i , k ) min(lcp(i,j),lcp(j,k)) \ge lcp(i,k) min(lcp(i,j),lcp(j,k))≥lcp(i,k)。 x = m i n ( l c p ( i , j ) , l c p ( j , k ) ) > l c p ( i , k ) x=min(lcp(i,j),lcp(j,k)) > lcp(i,k) x=min(lcp(i,j),lcp(j,k))>lcp(i,k)不成立。 i ⋯ j i \cdots j i⋯j之间至少有公共前缀x, j ⋯ k j\cdots k j⋯k之间至少有公共前缀x。故i,j至少有公共前缀x。
推论三 (LCP Theorem): i < j , l c p ( i , j ) ≤ min ⁡ k : i + 1 j l c p ( k , k − 1 ) i < j,lcp(i,j) \le \min_{k:i+1}^j lcp(k,k-1) i<j,lcp(i,j)≤mink:i+1jlcp(k,k−1)。用数学归纳法证明: j − i = 1 j-i=1 j−i=1时,符合。当 j − i = l e n 1 j-i=len1 j−i=len1时成立,则 j = l e n 1 + 1 j=len1+1 j=len1+1也成立,就是推论二,对应参数分别为(i,i+len1,j)。
推论四 (LCP Corollary): 对i≤j<k,LCP(j,k) ≥ \ge ≥LCP(i,k)。利用推论三容易证明。
小结一 :求最大lcp,只需要比较字典序相邻的后缀。
性质五 :不存在相等的后缀,因为两者长度不一样。

height0=0,heighti = lcp(i,i-1)。 max ⁡ ( h e i g h t ) \max(height) max(height)便是答案。

hi=hrank\[i]。
性质六 : i > 0 , h i ≥ h i − 1 − 1 i >0,hi \ge hi-1-1 i>0,hi≥hi−1−1。

当 h i − 1 ≤ 1 hi-1 \le 1 hi−1≤1时,显然成立。

下面讨论 h i − 1 > 1 hi-1 >1 hi−1>1
j = i − 1 , k = s a r a n k \[ j − 1 ] j = i-1,k=sarank\[j-1] j=i−1,k=sarank\[j−1]

显然有:suff(k) < suff(j),即rankk < rankj

根据性质六一:

lcp(rank(j),rank(k)) = lcp(rank(i),rank(k+1))+1

即:lcp(rank(i),rank(k+1)) = lcp(rank(j),rank(k))-1 = hi-1-1

根据性质六二及rankk < rankj

rankk+1 < ranki,即rankk+1 ≤ \le ≤ranki-1

根据推论四: hi = lcp(ranki,ranki-1) ≥ \ge ≥ lcp(rank(i),rank(k+1))得证
性质六一 : l c p ( r a n k ( i ) , r a n k ( j ) ) > 1 lcp(rank(i),rank(j))>1 lcp(rank(i),rank(j))>1。则lcp(rank(i),rank(j))= lcp(rank(i+1),rank(j+1))+1。

sarank\[i-1]
性质六二 : l c p ( r a n k ( i ) , r a n k ( j ) ) > 1 lcp(rank(i),rank(j))>1 lcp(rank(i),rank(j))>1。suff(i)< suff(j),则suff(i+1)<suff(j+1)。
小结二:i从小到大计算hi,hi = max(hi-1-1,0)开始枚举,无需复位,只需要每次减少n。故时间复杂度O(n)。h0从0开始枚举。

后缀树

这个更复杂,以后研究。

代码

模板代码

cpp 复制代码
class CSuffArr {
		public:
			CSuffArr(const string& str) :N(str.length()), m_str(str) {
				const int M = max(N, 26)+1;
				vector<vector<pair<int,int>>> bucket(M);	
				for (int i = 0; i < N; i++) {			
					bucket[str[i]-'a'+1].emplace_back(i, 1);
				}
				BucketToRes(bucket);
				for (int len = 1; len < N; len *= 2) {
					bucket.assign(M,vector<pair<int,int>>());
					for (int i = N - len ; i < N; i++) {//长度小于等于len
						bucket[m_rank[i]].emplace_back(i,0);
					}
					for (const auto& i : m_sa) {
						if (i >= len) {
							const int iPre = i - len;
							bucket[m_rank[iPre]].emplace_back(iPre,m_rank[i]);
						}
					}
					BucketToRes(bucket);
				}
			}

模板题:力扣1044

核心代码

cpp 复制代码
	class Solution {
		public:
			string longestDupSubstring(string s) {
				CSuffArr suffArr(s);
				auto h = suffArr.GetH();
				const int iMaxInx = max_element(h.begin(), h.end()) - h.begin();
				return s.substr(iMaxInx,h[iMaxInx]);
			}
		};

单元测试

cpp 复制代码
string s;
		TEST_METHOD(TestMethod11)
		{
			s = "banana";
			auto res = Solution().longestDupSubstring(s);
			AssertEx(string("ana"), res);
		}
		TEST_METHOD(TestMethod12)
		{
			s = "abcd";
			auto res = Solution().longestDupSubstring(s);
			AssertEx(string(""), res);
		}

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
员工说:技术至上,老板不信;投资人的代表说:技术至上,老板会信。
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境: VS2022 C++17

如无特殊说明,本算法 用**C++**实现。

相关推荐
KaMeidebaby8 小时前
卡梅德生物技术快报|羊驼免疫:分子生物学实战:基于羊驼免疫的重链抗体制备与全流程验证方案
前端·网络·数据库·人工智能·算法·百度
oort1238 小时前
AI+基层治理·智慧政务解决方案——AI民意速办智能助手
大数据·人工智能·算法·政务
渡之9 小时前
GeoBridge 深度解析:语义锚定多视图基础模型,重塑无人机跨视角地理定位
深度学习·算法·动态规划·无人机
一口吃俩胖子9 小时前
【脉宽调制DCDC功率变换学习笔记024】电压反馈补偿和环路增益
笔记·学习·算法
洛水水9 小时前
【力扣100题】80.寻找旋转排序数组中的最小值
数据结构·算法·leetcode
ting94520009 小时前
VC Boom 技术架构与核心算法深度解
人工智能·算法·架构
无限码力9 小时前
美团研发岗 5月9号笔试真题 - 正整数矩阵
算法·美团笔试真题·美团研发岗笔试真题·美团0509笔试真题
Rabitebla9 小时前
C++ 多态详解:从概念到虚表底层原理(代码轰炸)
开发语言·c++
Smilecoc9 小时前
决策树(二):决策树的划分选择
算法·决策树·机器学习
charlie1145141919 小时前
通用GUI编程技术——图形渲染实战(五十)——命中测试与鼠标事件路由:精确交互
c++·windows·架构·交互·图形渲染