后缀数组模板

news2024/11/16 13:52:29

详细理解后缀数组求sa数组的函数,该函数可以看为主要分为三个部分,第一个部分是预处理;第二个部分是进行基数排序,首先根据第二关键词排序,然后根据第一关键字排序;第三个部分是根据排序后的结果重新为每个字符串分配桶。后两个部分以倍增的形式重复,直到排序结束。

理解各个数组的含义

x[i]:记录原始下标为i的字符串所在桶的编号
c[i]:记录编号为i的桶,在所有桶中的累计价值,也就是前缀和,在求前缀和之前,要求编号为i的桶里所装的字符串的个数
y[i]:辅助数组,当对sa数组进行改变时,提前存一下sa数组,当对x数组进行改变时,提前存一下x数组
sa[i]:排序后下标为i的字符串在原始序列中的下标

第一部分,预处理

根据所有后缀字符串的第一个字符进行排序
第一个for循环:s表示的是原始字符串,那么对于下标为i的后缀字符串,s[i]存的就是它的第一个字符,初始时,直接按照第一个字符的值分配桶,所以有

x[i]=s[i]

,同时还要统计x[i]号桶此时存的字符串数量,所以有

c[x[i]=s[i]]++

第二个for循环:求每个桶的累计价值,也就是前缀和,所以这里i <= m,m表示的是桶的个数
第三个for循环:求sa数组,这里就是根据桶里的累计值进行排序了,求出原始下标为i的字符串所在的桶编号,即x[i],然后求该桶在所有桶中的累计值c[x[i]],这个就是原始下标为i的字符串此时的排序,所以有sa[c[x[i]] = i,此时相当于我们从桶中取出了一个值,那么桶的累计值要减1,所以有

sa[c[x[i]]–] = i;

for(int i = 1;i <= n;i++) c[x[i]=s[i]]++;
for(int i = 1;i <= m;i++) c[i] += c[i-1];
for(int i = n;i >= 1;i--) sa[c[x[i]]--] = i;

倍增的框架

for(int k = 1;k <= n;k = k << 1) {}	

按照第二关键字排序

每次排序前我都要重新分配桶,所以先对c数组清空。
第一个for循环:每次排序我都要改变sa数组,但是在排序的过程中我又要用到上一轮得到的sa数组,所以用y数组暂存上一轮的sa数组。
第二个for循环:同预处理中的第一个for循环作用一样,只是不用再分配桶了,也就是没有了x[i]=s[i]这一步。此时是按照预处理排序后的顺序进行遍历的,不是原始序列的顺序,就是i表示的排序为i的字符串,c数组里用的是字符串在原始序列中的下标,所以这里要转换一下,求排序为i的字符串在原始序列中的下标,其实这也就是sa数组的作用,用sa数组转换即可,这里用到的应该是上一轮排序得到的sa数组,所以我们此时用y数组,即x[y[i]],这里就是排序后为i的字符串所在的桶,但是我这一轮是按照第二关键字进行排序的,所以应该有一个偏移,这个偏移加在哪里呢?自然是原始下标那里,所以应该是c[x[y[i]+k]]
第三个for循环:同预处理中的第二个for循环作用一样
第四个for循环:同预处理中的第三个for循环作用一样,因为i表示的是排序后的下标,所有多了转换的一步,同时sa[i]=j,这个j是原始序列的下标,所以有

sa[c[x[y[i]+k]]–] = y[i]

Arrays.fill(c, 0);
for(int i = 1;i <= n;i++) y[i] = sa[i];
for(int i = 1;i <= n;i++) c[x[y[i]+k]]++;
for(int i = 1;i <= m;i++) c[i] += c[i-1];
for(int i = n;i >= 1;i--) sa[c[x[y[i]+k]]--] = y[i];

按照第一关键字排序

这里同按照第二关键字排序排序一样,只是把偏移拿掉就可以了。

Arrays.fill(c, 0);
for(int i = 1;i <= n;i++) y[i] = sa[i];
for(int i = 1;i <= n;i++) c[x[y[i]]]++;
for(int i = 1;i <= m;i++) c[i] += c[i-1];
for(int i = n;i >= 1;i--) sa[c[x[y[i]]]--] = y[i];

重新分配桶

明确一点,只有在排序中相邻的两个字符串,才有可能相等,所以我们要判断排序中相邻的字符串就可以了,如何判断它们是否相等呢?看他们在上一轮中是否在同一个桶里。排序相邻的两个字符串sa[i],sa[i-1],是否在同一个桶里

y[sa[i]] == y[sa[i-1]

,注意,我们这里不应该只比较第一关键字,还要比较第二关键字

y[sa[i]+k] == y[sa[i-1]+k]

,若相等则把当前桶号给他

x[sa[i]] = m;

,否则自己重新开辟一个桶

x[sa[i]] = ++m;

。注意:当分配的桶的个数和后缀字符串的个数相等时就说明我们已经完成了排序,直接退出循环即可,

if(m == n) break;。

for(int i = 1;i <= n;i++) y[i] = x[i];//存一下桶
m = 0;//重新给桶编号
for(int i = 1;i <= n;i++) {
	if(y[sa[i]] == y[sa[i-1]] && y[sa[i]+k] == y[sa[i-1]+k]) x[sa[i]] = m;
	else x[sa[i]] = ++m;
if(m == n) break;
}

例子

给一个字符串为aabaaaab,它的所有后缀字符串如下图左半部分所示,右半部分是按照所有后缀字符串的第一个字符排序后的结果

第一轮排序(k=1):按照第二关键字进行排序后的结果如下图左半部分所示,右半部分是按照所有后缀字符串的第一关键字排序后的结果,
在这里插入图片描述
如下图所示,现在相当于对于每一个后缀字符串,按照前两个字符实现了排序,下图同时也展示了重新分配桶的结果。
在这里插入图片描述
第二轮排序(k=2):接下来k变成2了,按照第二关键字进行排序后的结果如下图左半部分所示,右半部分是按照所有后缀字符串的第一关键字排序后的结果,我们在这里可以再理解一下c[x[y[i]+k]]++;,对于aaab,上一轮排序后的它对应的下标应该是3,那么y[3]得到的应该是5,也就是最初的那个下标,那么此时k=2,aaab的第一关键字是aa,第二关键字是ab,那我们看x[y[i]+k]=x[5+2]=x[7],原始下标为7的字符串是ab,而此时它的有效字符也恰好是ab,所以y[i]+k可以代表aaab的第二关键字。
在这里插入图片描述
我在学的时候有一个问题,在前一轮中我们按照每个后缀字符串的前两个字符实现了排序,在这一轮按照第二关键字进行排序时,我是怎么比较的第二关键字的大小的呢?其实一个字符串的第二关键字,也是某一个字符串的第一个关键字,如下图所示,而在上一轮排序时我已经确定了字符串的第一个关键字的大小
在这里插入图片描述
如下图所示,在第二轮排序完成后,会发现此时桶的个数恰好等于后缀字符串的个数,我们的排序结束!
在这里插入图片描述

完整代码如下

private static void get_sa() {
	// TODO Auto-generated method stub
	for(int i = 1;i <= n;i++) c[x[i]=s[i]]++;
	for(int i = 1;i <= m;i++) c[i] += c[i-1];
	for(int i = n;i >= 1;i--) sa[c[x[i]]--] = i;
	for(int k = 1;k <= n;k = k << 1) {
		Arrays.fill(c, 0);
		for(int i = 1;i <= n;i++) y[i] = sa[i];
		for(int i = 1;i <= n;i++) c[x[y[i]+k]]++;
		for(int i = 1;i <= m;i++) c[i] += c[i-1];
		for(int i = n;i >= 1;i--) sa[c[x[y[i]+k]]--] = y[i];
		Arrays.fill(c, 0);
		for(int i = 1;i <= n;i++) y[i] = sa[i];
		for(int i = 1;i <= n;i++) c[x[y[i]]]++;
		for(int i = 1;i <= m;i++) c[i] += c[i-1];
		for(int i = n;i >= 1;i--) sa[c[x[y[i]]]--] = y[i];
		
		for(int i = 1;i <= n;i++) y[i] = x[i];//存一下桶
		m = 0;//重新给桶编号
		for(int i = 1;i <= n;i++) {
			if(y[sa[i]] == y[sa[i-1]] && y[sa[i]+k] == y[sa[i-1]+k]) x[sa[i]] = m;
			else x[sa[i]] = ++m;
			if(m == n) break;
		}		
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1314584.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Bytebase 2.12.0 - 改进自动补全和布局导航

&#x1f680; 新功能 支持 MySQL 高级自动补全。支持从 UI 上导入分类分级配置。 &#x1f514; 重大变更 作废已有企业版试用证书。之后可以通过提交申请获取新的试用证书。 &#x1f384; 改进 改进整体布局和导航。 支持在 SQL 编辑器里显示以及查询 PostgreSQL 数据…

DDOS 攻击是什么?有哪些常见的DDOS攻击?

DDOS简介 DDOS又称为分布式拒绝服务&#xff0c;全称是Distributed Denial of Service。DDOS本是利用合理的请求造成资源过载&#xff0c;导致服务不可用&#xff0c;从而造成服务器拒绝正常流量服务。就如酒店里的房间是有固定的数量的&#xff0c;比如一个酒店有50个房间&am…

继续看回溯问题

关卡名 继续看回溯问题 我会了✔️ 内容 1.复习递归和N叉树&#xff0c;理解相关代码是如何实现的 ✔️ 2.理解回溯到底怎么回事 ✔️ 3.掌握如何使用回溯来解决二叉树的路径问题 ✔️ 1 复原IP地址 这也是一个经典的分割类型的回溯问题。LeetCode93.有效IP地址正好由四…

生产环境_Spark处理轨迹中跨越本初子午线的经度列

使用spark处理数据集&#xff0c;解决gis轨迹点在地图上跨本初子午线的问题&#xff0c;这个问题很复杂&#xff0c;先补充一版我写的 import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.sql.{Row, SparkSession} import org.apache.spark.sql.func…

t-io 程序执行后,jvm不退出的原因

基于t-io 1.7.3 版本分析源码 1、设定当前时间&#xff0c;每10毫秒执行一次 (非守护线程) 2、对应线程池的核心线程在AioServer启动时全部激活&#xff0c;并且添加空任务到阻塞队列&#xff0c;让核心线程(非守护线程)一直存活

ArcGIS Pro SDK文件选择对话框

文件保存对话框 // 获取默认数据库var gdbPath Project.Current.DefaultGeodatabasePath;//设置文件的保存路径SaveItemDialog saveLayerFileDialog new SaveItemDialog(){Title "Save Layer File",OverwritePrompt true,//获取或设置当同名文件已存在时是否出现…

七. 使用ts写一个贪吃蛇小游戏

之前学习了几篇的ts基础&#xff0c;今天我们就使用ts来完成一个贪吃蛇的小游戏。 游戏拆解 我们将我们的任务进行简单拆解分析。 首先我们应该有一个窗口&#xff0c;我们叫做屏幕。让蛇在里面移动&#xff0c;所有我们应该想到要设计一个大盒子当作地图。考虑到食物以及蛇…

【Java代码审计】文件上传篇

【Java代码审计】文件上传篇 1.Java常见文件上传方式2.文件上传漏洞修复 1.Java常见文件上传方式 1、通过文件流的方式上传 public static void uploadFile(String targetURL, String filePath) throws IOException {File file new File(filePath);FileInputStream fileInpu…

【单调栈】【区间合并】LeetCode85:最大矩形

作者推荐 【动态规划】【广度优先搜索】LeetCode:2617 网格图中最少访问的格子数 本文涉及的知识点 单调栈 区间合并 题目 给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵&#xff0c;找出只包含 1 的最大矩形&#xff0c;并返回其面积。 示例 1&#xff1…

遥感图像分割系统:融合空间金字塔池化(FocalModulation)改进YOLOv8

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 遥感图像分割是遥感技术领域中的一个重要研究方向&#xff0c;它的目标是将遥感图像中的不同地物或地物类别进行有效的分割和识别。随着遥感技术的不断发展和遥感…

iOS_给View的部分区域截图 snapshot for view

文章目录 1.将整个view截图返回image&#xff1a;2.截取view的部分区域&#xff0c;返回image&#xff1a;3.旧方法&#xff1a;4.Tips参考&#xff1a; 1.将整个view截图返回image&#xff1a; 这些 api 已被废弃&#xff0c;所以需要判断 iOS 版本 写两套代码&#xff1a; R…

【Java】5分钟读懂Java虚拟机架构

5分钟读懂Java虚拟机架构 Java虚拟机&#xff08;JVM&#xff09;架构JVM是如何工作的&#xff1f;1. 类加载器子系统2. 运行时数据区3. 执行引擎 相关资料 本文阐述了JVM的构成和组件。每个Java开发人员都知道字节码经由JRE&#xff08;Java运行时环境&#xff09;执行。但他们…

php入门、安装wampserver教程

php声称是全世界最好的语言&#xff0c;今天这篇文章就带大家入门学习php&#xff0c;php和python、javasript一样&#xff0c;是一种弱类型的脚本语言。 一、php开发环境搭建 作为初学者&#xff0c;学习php建议安装wampserver&#xff0c;wampserver是包含了apache、php和mys…

oracle 锁表解决办法

相关表介绍 V$LOCKED_OBJECT&#xff08;记录锁信息的表&#xff09;v$session&#xff08;记录会话信息的表&#xff09;v$sql&#xff08;记录 sql 执行的表&#xff09;dba_objects&#xff08;用来管理对象&#xff0c;表、库等等&#xff09; 查询锁表的 SID select b.…

网络入门---可变参数原理和日志模拟实现

目录标题 前言有关函数的几个性质介绍可变参数的用法介绍可变参数的一个注意事项可变参数的底层原理va_listva_endva_startva_arg_INTSIZEOF 可变参数的注意事项日志的实现日志的测试 前言 在上一篇文章中我们介绍了TCP协议有关的函数&#xff0c;大致就是服务端先通过listen函…

Android多国语言翻译 国际化

语言目录详细对应关系 Arabic, Egypt (ar-rEG) —————————–阿拉伯语&#xff0c;埃及 Arabic, Israel (ar-rIL) ——————————-阿拉伯语&#xff0c;以色列 Bulgarian, Bulgaria (bg-rBG) ———————保加利亚语&#xff0c;保加利亚 Catalan, Spain (ca-r…

函数栈帧的创建和销毁(编程底层原理)

本篇的内容格外的难写&#xff0c;里面包含了许多的专业术语名和汇编指令等晦涩难懂的东西&#xff0c;既不利于讲解&#xff0c;也不利于读者的理解。但我会尽力去讲述出里面的底层逻辑&#xff0c;帮助大家去理解里面的过程&#xff0c;理解编程的底层原理可以为我们后续更为…

YOLOv8 | 代码逐行解析(一) | 项目目录构造分析

一、本文介绍 Hello&#xff0c;大家好这次给大家带来的不是改进&#xff0c;是整个YOLOv8项目的分析&#xff0c;整个系列大概会更新7-10篇左右的文章&#xff0c;从项目的目录到每一个功能代码的都会进行详细的讲解&#xff0c;同时YOLOv8改进系列也突破了三十篇文章&#x…

助力工业产品质检,基于yolov5l集成CBAM注意力机制开发构建智能PCB电路板质检分析系统

AI助力工业质检智能生产制造已经有很多成功的实践应用了&#xff0c;在我们前面的系列博文中也有很多对应的实践&#xff0c;感兴趣的话可以自行移步阅读前面的博文即可&#xff0c;这里本文的核心目的就是想要基于改进的yolov5l来开发构建用于PCB电路板智能检测分析的模型&…

GZ015 机器人系统集成应用技术样题1-学生赛

2023年全国职业院校技能大赛 高职组“机器人系统集成应用技术”赛项 竞赛任务书&#xff08;学生赛&#xff09; 样题1 选手须知&#xff1a; 本任务书共 25页&#xff0c;如出现任务书缺页、字迹不清等问题&#xff0c;请及时向裁判示意&#xff0c;并进行任务书的更换。参赛队…