后缀数组模板

详细理解后缀数组求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;
		}		
	}
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

等保二级和三级的区别

等保二级和三级定级标准有什么区别&#xff1f;定级原则和方法介绍 网络安全等级保护&#xff0c;简称等保&#xff0c;是我国为了保障信息系统的安全运行&#xff0c;防止信息泄露、篡改、破坏等威胁&#xff0c;制定的一套信息安全管理制度。根据《信息安全技术网络安全等级保…

6.21二叉搜索树的最近公共祖先(L235-M)

算法&#xff1a; 可以和上一题一样做&#xff0c;但是最好还是要用上二叉搜索树的特性 遍历顺序无所谓&#xff0c;因为中不用写逻辑代码。 假如p3&#xff0c;q5 若当前遍历节点&#xff08;比如6&#xff09;比p和q都大&#xff0c;说明p和q一定在当前节点的左子树里面 …

Python数值类型(整形、浮点型和复数)及其用法

数值类型是计算机程序最常用的一种类型&#xff0c;既可用于记录各种游戏的分数、游戏角色的生命值、伤害值等&#xff0c;也可记录各种物品的价格、数量等&#xff0c;Python 提供了对各种数值类型的支持&#xff0c;如支持整型、浮点型和复数。 Python整型 Python 3 的整型…

Intel® Enclave Operation(三)

文章目录 前言一、Constructing an Enclave1.1 ECREATE1.2 EADD and EEXTEND Interaction1.3 EINIT Interaction1.4 Intel SGX Launch Control Configuration 二、Enclave Entry and Exiting2.1 Controlled Entry and Exit2.2 Asynchronous Enclave Exit (AEX)2.3 Resuming Exe…

web服务器之——建立两个基于ip地址访问的网站

目录 准备工作&#xff1a;web服务器搭建 第一步&#xff1a;挂载 第二步&#xff1a;编辑配置文件 第三步&#xff1a;安装软件包 第四步&#xff1a;启动httpd 查看配置文件&#xff1a; 第五步&#xff1a;设置防火墙状态&#xff1a; 重启服务: 查看状态&#xff1…

自己开发App,如何能兼顾效率与体验?

今天来聊聊一个现实但不简单的问题&#xff1a;如何能够做到自己开发App。 首先&#xff0c;在搜索引擎搜索“自己开发App”&#xff0c;会冒出一大堆类“手把手”的内容&#xff0c;超级详细、稍微浏览一些内容的引言部分&#xff0c;乍一看好像还挺合理&#xff0c;但点击进…

多地远程视频监控,如何集中连接与管理?

如今&#xff0c;远程视频监控已广泛应用于商超零售、酒店、工厂工地、IT机房、农业生产、医疗保健、公共安全等多种场景。其中&#xff0c;网络通信技术是远程监控技术中最为关键的技术&#xff0c;远程监控数字化应用的增长对广域网等基础IT建设提出更高的需求。 以广东某连锁…

python实战教学之python版“张万森,好久不见”

前言 WINTER IS COMING 最近《一闪一闪亮星星》的电影在火热预售中&#xff0c;家人们抢到票了嘛&#xff0c;前两天小编写了一篇“张万森&#xff0c;下雪了”的文章后&#xff0c;收到了不少小伙伴的反馈&#xff1a;“代码的运行结果只有文字&#xff0c;没有雪花啊”&#…

气温波动 C语言xdoj45

问题描述 最近一段时间气温波动较大。已知连续若干天的气温&#xff0c;请给出这几天气温的最大波动值是多少&#xff0c;即在这几天中某天气温与前一天气温之差的绝对值最大是多少。 输入说明 输入数据分为两行。 第一行包含了一个整数n&#xff0c;表示给出了连续n天…

JNPF低代码——全源码、免费部署的开发框架

低代码平台的概念很火爆&#xff0c;产品也是鱼龙混杂。 对于开发人员来说&#xff0c;在使用绝大部分低代码平台的时候都会遇到一个致命的问题&#xff1a;我在上面做的项目无法得到源码&#xff0c;完全黑盒。一旦我的需求平台满足不了&#xff0c;那就是无解。 与其他平台的…

便签电脑版下载教程,电脑便签用哪个

现在大家所熟知的电脑便签软件通常以电脑软件为主&#xff0c;过去那种贴满五颜六色的&#xff0c;几百张成一叠的桌面便利贴&#xff0c;可以实现随处粘贴&#xff0c;现在几乎已经被淘汰了&#xff0c;取而代之的是科技化的电脑便签软件。 在查找电脑便签软件时&#xff0c;…

helpdesk的工作流程是什么?

helpdes在IT部门中是一个非常重要的部门&#xff0c;负责为用户提供技术支持和问题解决方案。为了有效地提供这些服务&#xff0c;helpdesk需要建立一个清晰而高效的工作流程。本文将介绍helpdesk工作的典型流程&#xff0c;并探讨每个阶段的重要性。 1、用户报告问题 通常&…

RCG Self-conditioned Image Generation via Generating Representations

RCG: Self-conditioned Image Generation via Generating Representations TL; DR&#xff1a;将图像的无监督表征作为&#xff08;自&#xff09;条件&#xff08;而非是将文本 prompt 作为条件&#xff09;&#xff0c;生成与原图语义内容一致的多样且高质量结果。视觉训练能…

Android :Paging (分页)加载数据-简单应用

1.Paging介绍&#xff1a; 安卓Paging是一种分页加载数据的方法&#xff0c;它基于无限滚动模式而设计&#xff0c;可以帮助应用更高效地利用网络带宽和系统资源。Paging库可以加载和显示来自本地存储或网络中更大的数据集中的数据页面&#xff0c;适用于以列表的形式加载大量…

VSCode配置记录

1. 修改代码背景颜色 1&#xff09;Shift Command P&#xff0c;搜索框输入&#xff1a;settings.json 2&#xff09;输入配置 {"workbench.colorCustomizations": {"editor.lineHighlightBackground": "#86e9e93d", # 修改鼠标所在行背景色…

自动化测试 —— Web自动化三大报错

Web自动化三大报错有哪些呢&#xff1f;接下来给大家讲讲。 Web自动化三大报错&#xff08;Exception&#xff09; 1. Exception1&#xff1a;no such element&#xff08;没有在页面上找到这个元素&#xff09; reason1&#xff1a;元素延迟加载了 solution&#xff1a; …

功率放大器有哪些功能和作用

功率放大器是一种电子设备&#xff0c;主要用于将输入的低功率信号放大为更大的功率信号。功率放大器的主要功能和作用包括&#xff1a; 信号放大&#xff1a;功率放大器可以将输入的低功率信号放大为更大的功率信号。这对于一些需要输出更大功率的应用来说非常重要&#xff0c…

外包干了3年,技术退步太明显了。。。。。

先说一下自己的情况&#xff0c;本科生生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了差不多3年的功能测试&#xff0c;今年国庆&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能…

腾讯云服务器购买:腾讯云服务器购买指南一步步全流程攻略

腾讯云服务器购买流程直接在官方秒杀活动上购买比较划算&#xff0c;在云服务器CVM或轻量应用服务器页面自定义购买价格比较贵&#xff0c;但是自定义购买云服务器CPU内存带宽配置选择范围广&#xff0c;活动上购买只能选择固定的活动机&#xff0c;选择范围窄&#xff0c;但是…

正点原子高速无线下载器下载bin文件

有时候需要帮忙调试&#xff0c;直接下载写好代码的bin文件比较快&#xff0c;所以找到这个方式&#xff0c;关于keil如何生成bin文件可以看上篇文章&#xff0c;其他IDE生成方式我就遇到再说了&#xff0c;可以自己在网上搜教程。 关于正点原子的高速无线下载器可以去下载官方…