后缀数组看上去是一个很高深,很玄妙的东西。
但首先,我们要树立一个观点:它!不难!!
实际上,他也真的不难。难的只是其中一个想法的操作。除此以外,不难。
首先,我们需要了解一下什么是后缀
后缀,顾名思义,缀在后面的东西。在字符串中,就是缀在字符串后面的东西,大家可以联系一下英语单词中的后缀来理解。
举个例子
字符串:abaab
后缀分别有:
abaab, baab, aab, ab, b
相信到这里,大家已经理解了后缀的概念。那么接下来,我们来看一下什么是后缀数组。
常用的后缀数组通常有以下几个数组变量(后缀数组的核心):
int sa[maxn];
int rank[maxn];
以下的排序指字符串按字典序进行排序。
sa数组:sa数组的下标和字符串的下标一一对应,sa[0]就表示substr(0, n)排完序之后的次序。
rank数组:和sa数组正好相反,rank[i]=j的含义是排名第i的字符串在所给字符串的位置j处(这里字符串的位置当然是指字符串第一个字符在所给字符串中的位置)
总结:sa 和 rank数组处于一种互补的状态,可以这么来区分两个数组,sa表示谁第几名,rank表示第几名的是谁。
好了,到这,后缀数组的概念就差不多了。
是不是很简单??
诶!别急!继续看下去!
可能很多小伙伴的第一想法就是建立一个String数组,把题目给的字符串的所有后缀子串存进去。再来一个sort快排就完事了。
这确实是一个方法,但是太暴力了。快排的时间复杂度是O(nlogn),但是我们字符串大小比较不同于数字的大小比较,字符串的大小比较也是一个O(n)的时间复杂度,这么一来,这个算法的时间复杂度就是O(n^2logn),很显然,这是不可取的!!
这也就是后缀数组的难点了,怎么样对后缀子串进行高效的排序,获得sa,rank数组的值。
在这,给大家介绍一种方法,倍增算法
算法的大致思想如下:
换一种排序的思路,不是真的对所有的后缀子串进行排序并交换位置(即最开始暴力算法中的sort)。而是通过一定的规则我们来计算后缀子串的排名。具体思路如下:
第一次排序:对每个后缀子串的前1个字符进行排序
第二次排序:对每个后缀子串的前1—2个字符进行排序
第三次排序:对每个后缀子串的前1—4个字符进行排序
…
第n次排序:对每个后缀子串的前1—2^(n-1)个字符进行排序
????这啥意思???为什么这么做??
上图!!
图片其实也有一点点抽象,对着图片和下面的文字我们来理解一下。
倍增,可以理解为每次比较的字符个数,是按照2的指数增长的(也就是2倍增)。
为什么采取这种措施?
比方说我们现在要进行第三次排序,也就是对前4个字符进行排序,那么在此之前的第二次排序,我们一定已对后缀字符的前2个字符进行了排序(再次强调!这里的排序是“假“的!只是通过比较获得次序而已)
所以此时,我们可以通过第二次排序的结论来进行第三次排序。
计算任一后缀子串k的前4个字符的次序号z可以通过比较后缀子串k的前2个字符的次序号x和后缀子串k+2的前两个字符的次序号y,进而得到一个而元祖(x,y),这是我们后面进行比较排序的资本!
计算任一后缀子串k的前4个字符的次序号z可以通过比较后缀子串k的前2个字符的次序号x和后缀子串k+2的前两个字符的次序号y,进而得到一个而元祖(x,y),这是我们后面进行比较排序的资本!
计算任一后缀子串k的前4个字符的次序号z可以通过比较后缀子串k的前2个字符的次序号x和后缀子串k+2的前两个字符的次序号y,进而得到一个而元祖(x,y),这是我们后面进行比较排序的资本!
大家对照图片好好理解一下这句话!
获得了这个资本之后呢,我们就可以进行排序啦。
比如:
若两个后缀子串的而元祖分别为(x1, y1) (x2, y2), 排序分别为z1,z2
若 x1 代码:#include