- 浏览: 543386 次
- 性别:
- 来自: 上海
文章分类
- 全部博客 (231)
- 一个操作系统的实现 (20)
- 汇编(NASM) (12)
- Linux编程 (11)
- 项目管理 (4)
- 计算机网络 (8)
- 设计模式(抽象&封装) (17)
- 数据结构和算法 (32)
- java基础 (6)
- UML细节 (2)
- C/C++ (31)
- Windows (2)
- 乱七八糟 (13)
- MyLaB (6)
- 系统程序员-成长计划 (8)
- POJ部分题目 (10)
- 数学 (6)
- 分布式 & 云计算 (2)
- python (13)
- 面试 (1)
- 链接、装载与库 (11)
- java并行编程 (3)
- 数据库 (0)
- 体系结构 (3)
- C++ template / STL (4)
- Linux环境和脚本 (6)
最新评论
-
chuanwang66:
默默水塘 写道typedef void(*Fun)(void) ...
C++虚函数表(转) -
默默水塘:
typedef void(*Fun)(void);
C++虚函数表(转) -
lishaoqingmn:
写的很好,例子简单明了,将观察者模式都表达了出来。
这里是ja ...
观察者模式——Observer
一、后缀数组 及其对应的名次数组
举例:S=" B A C $ " , 后缀数组长度为n-1= 3 , 假定'$'<字符集Σ中的任意字符
1 2 3 4
1. 后缀数组SA=[4 2 1 3] , 对应所有后缀的一个字典序 从小到大的序列
SA[1]=4 --> 对应 "$"
SA[2]=2 --> 对应 "A C $"
SA[3]=1 --> 对应 "B A C $"
SA[4]=3 --> 对应 "C $"
2. 名次数组Rank=SA^-1, 方便地查询每个后缀在后缀数组中的名次
Rank[4]=1 --> Suffix(4)="$" 在后缀数组中排第1
Rank[2]=2 --> Suffix(2)="A C $" 在后缀数组中排第2
Rank[1]=3 --> Suffix(1)="B A C $" 在后缀数组中排第3
Rank[3]=4 --> Suffix(3)="C $" 在后缀数组中排第4
一、构造后缀数组——“k-前缀比较”的倍增方法(Doubling Algorithm)
1. k-前缀
字符串u的
k-前缀
:
两个字符串的
k-前缀比较关系
:
2. “k-前缀比较”的3个性质
A. 性质1.2、 1.3 使得我可以倍增的重排:
“1-前缀排序”的前缀数组SA_1[]=Suffix(i)...
“2-前缀排序”的前缀数组SA_2[]=Suffix(i)...
“4-前缀排序”的前缀数组SA_4[]=Suffix(i)...
“8-前缀排序”的前缀数组SA_8[]=Suffix(i)...
...
“k-前缀排序”的前缀数组SA_k[]=Suffix(i)...
注: 所有SA_i中的元素都是一样的(字符串的所有可能后缀),只不过排列顺序不同
B. 性质1.1 当k≥n时,“字典顺序” 和 “k-前缀顺序”就是一个东东了!
告诉我们当k≥n时,就可以结束了:) 因为此时SA_k[]中后缀顺序即是“所有后缀按照字典顺序的排列”了。
3. 基于k-前缀的构造后缀数组(及其名次数组)
初始化: 从 “1-前缀 前缀数组SA_1” 开始; 计算出她对应的 “1-前缀 名次数组Rank_1” ; //O(n)时间 (Rank=SA^-1) while(k<n){ //O(nlogn)时间, // A. “2k-前缀比较”Suffix_2k(i)和Suffix_2k(j)用时O(1) // B. 从小到大快排所有Suffix_2k(i)用时O(nlogn)(注:大小比较是=2k, <2k这样的“2k-前缀比较”) 性质2.3,用“k-前缀 前缀数组SA_k” 和 “k-前缀 名次数组Rank_k” 构造 “2k-前缀 前缀数组SA_2k” ; //O(n)时间 (Rank=SA^-1) 计算 “2k-前缀 前缀数组SA_2k” 对应的 “2k-前缀 名次数组Rank_2k” ; }
三、height[]数组 height[i]=lcp( Suffix(SA[i-1]), Suffix(SA[i]) )
区别和明确几个定义: lcp(u,v)和LCP(i,j); height[]和h[]的定义:
(1)任意两个字符串的“最长公共前缀长度 ”: lcp(u,v) = max{i | u =i v}, =i表示“i-前缀等于”关系
int lcp(String u, String v);
(2)后缀数组中两个元素对应字符串的“最长公共前缀长度 ”:LCP(i,j) = lcp( Suffix(SA[i]), Suffix(SA[j]) )
int LCP(int i, int j);
注意:i和j是在后缀数组SA[]中的排名/位置 ,不对应原字符串中后缀的开始位置;
SA[i]和SA[j]才对应了这两个后缀 开始的字符 在原字符串中的位置 。
LCP Theorem :
设i<j,则LCP(i,j) = min{ LCP(k-1,k) | i+1≤k≤j } = min{ height[i+1], height[i+2], ... , height[j] }
(3)后缀数组中“第i-1个元素”和“第i个元素”的“最长公共前缀长度 ”
height[i] = LCP(i-1, i) = lcp( Suffix(SA[i-1]), Suffix(SA[i]) )
(4)原字符串中“从位置i开始的后缀”和“该后缀在SA[]中前一个元素(另一个后缀)”的“
最长公共前缀长度
”
h[i] = height[ Rank[i] ] = height[Rank[i]-1, Rank[i]] = lcp( Suffix(Rank[i]-1), Suffix(Rank[i]) )
注意:
(1)
height[i]中i 含义是 “目标后缀是后缀数组SA[]中的第i个元素/SA[]中排名第i的元素/字典序第i小的元素”
h[i]中的i 含义是 “目标后缀是从原字符串中位置i开始的后缀S[i, ..., n]”. 令,原字符串为S[1, ..., n]
(2)数组名称总是代表函数值的含义
sa[排名]=后缀字符串
rank[后缀字符串]=排名
模板代码:我的这个模板改造了罗穗骞的代码,使之更容易理解
///////////////////////////////////////////////////////////////// //Constructing Suffix Array with Doubling Algorithm //Constructing Height[] ///////////////////////////////////////////////////////////////// #include <algorithm>//sort #include <cstring>//memset #include <iostream> using namespace std; const int MAX_SFX = 210000; struct Sfx { //i --> 后缀在“原数组”中的位置(唯一确定了一个后缀字符串) int i; //key[0] --> 后缀S[i,...,n]在 “k-前缀排序”中的位置 //key[1] --> 后缀S[i+k,...,n]在“k-前缀排序”中的位置 int key[2]; bool operator < (const Sfx& s) const //后面的const是什么意思??? { return key[0] < s.key[0] || (key[0] == s.key[0] && key[1] < s.key[1]); } }; int g_buf[MAX_SFX + 1]; Sfx g_tempSfx[2][MAX_SFX]; Sfx *g_sa = g_tempSfx[0]; //后缀数组 g_sa[0]~g_sa[len-1] int rank[MAX_SFX]; //名次数组 rank[0]~rank[len-1] int height[MAX_SFX]; //height数组 height[0]~height[len-1]. 注:包括 height[0](==0) /* CALL: cSort(in, len, key∈{0,1}, out); EFFECT: in[0]~in[len-1],按照域 in[i].key[key]进行排序,结果放入out中 理解基数排序: 假设有4个字母 in[]={A B B A},即,有2个A;有2个B. 即,有2个≤A ;有4个≤B 现在按照字母顺序放置这4个字母 in[]={A B B A} 方法: cnt[A]=2 cnt[B]=4 out[--cnt[B]]=B, 即out[--4]=B,即 out[3]=B out[--cnt[B]]=B, 即out[--3]=B,即 out[2]=B out[--cnt[A]]=A, 即out[--2]=A,即 out[1]=A out[--cnt[A]]=A, 即out[--1]=A,即 out[0]=A */ void cSort(Sfx* in, int n, int key, Sfx* out) { //cnt[]: cnt[i]表示 in[i].key[1]的值≤i的 in[i]的个数 int* cnt = g_buf; memset( cnt, 0, sizeof(int) * (n + 1) ); for (int i = 0; i < n; i++){ cnt[ in[i].key[key] ]++; } for (int i = 1; i <= n; i++){ cnt[i] += cnt[i - 1]; } for (int i = n - 1; i >= 0; i--){ //输入元素数组中的元素 in[i] 应该放到输出数组中的位置 out[...] out[ --cnt[ in[i].key[key] ] ] = in[i]; } } /* Build a suffix array from string 'text' whose length is 'len'. write the result into global array 'g_sa'. */ void buildSA(char* text, int len) { Sfx *temp = g_tempSfx[1]; int* rank = g_buf; //1. g_sa[]中后缀数组按照 1-前缀关系从小到大排列 for (int i = 0; i < len; i++){ g_sa[i].i = i; g_sa[i].key[0] = text[i]; g_sa[i].key[1] = i; //这句省略后结果相同 } sort(g_sa, g_sa + len); for (int i = 0; i < len; i++) { g_sa[i].key[1] = 0; } //2. 每次循环计算 “k-前缀关系 名次数组” 和 “2k-前缀关系 后缀数组” int k = 1; while (k < len) { //2.1 计算 “k-前缀关系 名次数组” //计算 k-前缀关系名次数组rank[]前,g_sa[]已经按照 k-前缀关系 ↑排序了 rank[ g_sa[0].i ] = 1; for (int i = 1; i < len; i++){ rank[ g_sa[i].i ] = rank[ g_sa[i - 1].i ]; if( g_sa[i-1] < g_sa[i] ){ rank[ g_sa[i].i ]++; } } //2.2 计算 “2k-前缀关系 后缀数组” // 2.2.A 设置了g_sa[]中每个元素的 i, key[0], key[1]三个域 for (int i = 0; i < len; i++){ g_sa[i].i = i; //i --> 后缀在“原数组”中的位置(唯一确定了一个后缀字符串) g_sa[i].key[0] = rank[i]; //key[0] --> 后缀S[i,...,n]在 “k-前缀排序”中的位置 g_sa[i].key[1] = i + k < len? rank[i + k]: 0; //key[1] --> 后缀S[i+k,...,n]在“k-前缀排序”中的位置 } // 2.2.B 根据 key[0], key[1]两个域,从小到大排列 g_sa[]中的每个元素 cSort(g_sa, len, 1, temp); cSort(temp, len, 0, g_sa); k *= 2; } } /* Build height[] 注:朴素算法计算height[]需要 O(n^2); 这里采用罗穗骞的算法,按照h[i]的顺序计算,时间复杂度为O(n) */ void buildHeight(char* str, Sfx* sa, int len){ //构造名次数组 for(int i=0; i<len; i++) rank[ sa[i].i ] = i; //按照 h[1], h[2], ... , h[n]的顺序计算,即按照 height[ rank[1] ], height[ rank[2] ],...顺序计算 //这样计算h[]的累积时间为O(n) // h[]定义:h[i] = height[ rank[i] ] // h[]性质:h[i] >= h[i-1]-1 有点像单调增:不会大退步 int k=0; for(int i=0; i<len; i++){ //此刻, k==h[i-1] if(k>0) k--; //此刻,k==h[i-1]-1 //j: 后缀数组中排在Suffix(i)前一位的后缀的位置 int j=sa[ rank[i]-1 ].i; //h[i]≥h[i-1]-1 == k, 只需将 k从 h[i-1]增长到 h[i]即可 while(str[i+k]==str[j+k]) k++; //相当于计算 h[i],因为height[rank[i]] = h[i] height[ rank[i] ] = k; } } int main() { /* 字符串尾部填充'\0',(int)'\0'为0,小于所有字符的值*/ char str[] = "aabbaaababab"; buildSA(str, 13); buildHeight(str, g_sa, 13); //The first suffix is useless (empty). for (int i=0; i<13; i++){ cout <<g_sa[i].i <<' '<<str+g_sa[i].i <<' '<<height[i] <<endl; } system("pause"); return 0; }
四、后缀数组的应用
1. 最长公共前缀
对于j, k,则有(我起的名)“最长公共前缀性质 ”:
Suffix(j)和Suffix(k)是同一个字符串的两个后缀 。,不妨设rank[j]<rank[k],则Suffix(j)和Suffix(k)的最长公共前缀为height[rank[j]+1], height[rank[j]+2], height[rank[j]+3], ... , height[rank[k]]中的最小值。这样就可以用RMQ问题的几种解法(线段树/ST算法)。
2. 单个字符串相关问题
例一、给定一个字符串,求最长重复子串
解法:O(n): 查找height[]中最大元素值
例二、给定一个字符串,求最长不重叠重复子串
解法:
什么是不重叠最长子串呢,就是一个串中至少出现两次,又不重叠的子串中的最长的,比较绕口。
解决这个问题的关键还是利用height 数组。把排序后的后缀分成若干组,其中每组的后缀之间的height 值都不小于k。然后找出各个组的后缀的sa值的最大最小值max,min,如果存在 max-min >= k,那么就存在长度为k的不重叠子串,因为根据LCP定理,每个组中的height值都不小于k,就是说这个组中这些后缀的最长公共前驱最小是k,然后由于max-min>= k,所以可以判定存在所要求的子串。做题的时候二分答案,把问题变为判定性问题(大牛说的)。那么整个事件复杂度为O(nlogn)。把height数组分组的思想非常常用,可以多看看IOI论文了解了解。
(1)把题目变成判定性问题“判断是否存在两个长度≥k的两个相同子串,且不重叠”。将后缀数组分为若干组。其中后缀之间height值≥k的后缀在同一组,显然只有这样的组才可能出现长度≥k的两个相同子串,且不重叠。
(2)对于满足上述要求的组判断每个后缀的sa值的最大值和最小值之差是否≥k(≥k则不重叠)。
注:基于这个问题的扩展见POJ 1743 Musical Theme 传说中楼教主男人八题之一。
例三、给定一个字符串,求至少出现k次的最长重复子串(这k个子串可以重叠)???
例四、子串的个数——给定一个字符串,求不相同的子串的个数???
发表评论
-
快排备忘
2013-10-26 11:27 582http://hi.baidu.com/pluto455988 ... -
LRU简单实现C++
2013-10-17 10:52 4026页面置换算法: 在地址映射过程中,若在页面中发现所要访问的页 ... -
二叉树相关
2013-10-04 17:40 726本节主要是为了写二叉树类型题目练手的代码,重点培养运用“指针 ... -
双指针策略(《编程之美》3.5 最短摘要生成)
2013-03-26 15:17 2251本文源自《编程之美》3.5 最短摘要生成一课。 ... -
K-MEDOIDS聚类算法
2012-12-04 21:18 2001k-medoids聚类算法 (wiki上讲得很清楚啊:) ) ... -
搜索(三)——回溯
2012-11-23 15:23 1055一、回溯 和 深度搜索 ... -
哈希Hash
2012-11-21 14:42 1008要点一:哈希表长度 的确定是个两难的问题:太短则容 ... -
状态压缩DP
2012-11-14 20:27 758引例、 例1、 例2、 例3、 ... -
模拟退火
2012-10-28 16:34 1233引用:http://www.cnblogs.com/heaad ... -
计算几何(一)——基础算法
2012-07-12 21:07 1931待续 《计算几何资料》为提纲 1. (1) 有向线段 ... -
计算几何(二)——平面最近点对
2012-07-12 10:54 922参考资料: 为何这个问题采用分治法 http://blog ... -
气泡滚大——剔除线性数据中的杂质
2012-06-18 09:43 972这是一道Java的面试题,但是我总结了除了一种自称为“ ... -
深入理解二分查找(二、二分答案)
2012-04-29 16:24 7218二分答案 如果已知候选答案的范围[min,max ... -
P问题、NP问题和NPC问题
2012-04-25 16:36 1080这篇文章转自这里 ... -
RMQ(Range Minimum/Maximum Query)区间最值查询
2012-04-18 20:47 1578一、RMQ问题描述 和 几种解题思路 RMQ问题 (Rang ... -
大整数运算(部分转载)
2012-04-12 21:36 1203待补充:“浮点数高精度运算” 参见这里==> ... -
单向链表相关
2012-04-10 16:42 994单向链表是微软笔试常考的,参考这里:http://www.c ... -
关键路径(AOE)
2012-04-10 08:05 1684前面这段话是引用别人的,后面代码是自己写的,有待完善: ... -
搜索(二)——双向BFS
2012-03-24 17:18 3036参考这篇文章,以下前 ... -
搜索(一)——剪枝
2012-03-24 11:46 1568参考文档:《搜索方法 ...
相关推荐
后缀数组是字符串处理中的一个重要概念,它在解决与字符串相关的问题时表现出强大的威力。罗穗骞的《后缀数组——处理字符串的有力工具》详细介绍了这一数据结构及其应用,包含算法源码和解题源码,对于学习者来说是...
后缀数组是字符串处理中的一种重要数据结构,它在算法竞赛(如ACM)和实际编程问题中有着广泛的应用。本文将深入探讨后缀数组及其相关知识点,帮助你更好地理解和运用这一工具。 后缀数组(Suffix Array)的概念...
后缀数组是字符串处理中的一个重要数据结构,尤其在算法竞赛(如OI)和文本处理领域广泛应用。罗穗骞,可能是某位知名的OI教练或专家,提供了关于后缀数组的源码和相关题目,帮助学习者深入理解这一概念。 后缀数组...
后缀数组是字符串处理中的一种重要数据结构,它在计算机科学特别是文本算法领域扮演着关键角色。后缀数组能够提供一种高效的方式来存储和查询字符串的后缀,这使得它成为解决许多字符串问题的有效工具,例如查找模式...
### 后缀数组的概念与用法 #### 一、引言 后缀数组作为一种重要的数据结构,在字符串处理领域有着广泛的应用。相比于后缀树,后缀数组不仅在实现上更为简便,而且在空间效率上也表现出明显优势。本文旨在详细介绍...
### 后缀数组的倍增法实现 #### 一、后缀数组简介 后缀数组是一种数据结构,用于存储一个字符串的所有后缀按照字典序排序的结果。它在字符串匹配问题中有广泛的应用,例如查找模式串在文本串中的位置、最长公共...
后缀数组是一种高效处理字符串的数据结构,在信息学竞赛和许多字符串处理的实际应用中占据着重要地位。它不仅与后缀树具有相似的功能,而且在编程实现上更加简洁,占用内存也更少,成为后缀树的一个实用替代品。 一...
后缀数组是一种在计算机科学中用于处理字符串的高效数据结构,尤其在文本处理和字符串搜索领域有着广泛应用。本文将深入探讨后缀数组及其相关概念,包括SA(Suffix Array)、Height、RMQ(Range Minimum Query)以及...
后缀数组是计算机科学中一种重要的数据结构,主要用于处理字符串问题。它在字符串搜索、模式匹配、DNA序列分析等领域有着广泛的应用。后缀数组的概念首次由Manber和Myers在1993年提出,其核心思想是将一个字符串的...
《基于压缩后缀数组的字符串搜索库解析》 在信息技术领域,高效地处理字符串搜索问题至关重要,尤其是在大数据分析和文本挖掘等应用场景中。压缩后缀数组(Compressed Suffix Array, CSA)作为一种节省存储空间的...
后缀数组是一种在字符串处理中极其重要的数据结构,由许智磊在IOI2004国家集训队论文中介绍。它是一个一维数组,包含字符串的所有后缀按照字典顺序排序后的起始索引。后缀数组的构建是通过特定算法实现的,如O(nlogn...
后缀数组是计算机科学中处理字符串的一种重要数据结构,它在文本索引、字符串搜索、生物信息学等领域有着广泛的应用。后缀数组的概念源于1990年代,由Udi Manber首次提出,其核心思想是将一个字符串的所有后缀按照...
本篇文章将探讨四个关键概念:树状数组、后缀数组、字典树以及多串匹配算法,这些都属于字符串处理和高效计算的重要工具,并提供一些实际应用的启示。 1. **树状数组(Counting Array / Fenwick Tree)** 树状数组...
后缀数组的构造和应用基础 后缀数组是一种数据结构,用于处理字符串的搜索和匹配问题。它将字符串的所有后缀排序后的结果储存在一个数组中,每个元素 sa[i] 储存的是排名为 i 的后缀的开始位置。后缀数组的构造可以...
后缀数组的实现和应用 后缀数组是处理字符串的有力工具之一,它可以高效地解决许多字符串问题。下面我们将详细介绍后缀数组的实现和应用。 后缀数组的实现 后缀数组是指一个字符串的所有后缀的排序结果。其中,SA...
后缀数组是一种在字符串处理中非常重要的数据结构,它被广泛应用于信息学竞赛和算法设计中。后缀数组可以看作是一系列字符串后缀的排序,其中每个元素都是原字符串的一个后缀,按字典序排列。相比于后缀树,后缀数组...