什么叫后缀数组 首先要知道什么叫后缀 ?html
好比 字符串 abcdef 那么 abcdef bcdef cdef def ef f 就叫作后缀 也就是从最后一个字母以前的一个字母开始一直到最后一个字母(因此所 bcd不是后缀 由于没有到最后一位f) 所构成的字符串就叫作后缀 算法
至于后缀数组能干什么?我在这就不介绍了 这不是本文的重点!本文主要讲解后缀数组应该怎么写代码!数组
写本文的缘由大数据
可是本身以前读过不少后缀数组的文章 短短二三十代码 却没有找到一篇博客从头至尾讲解的url
多是由于我没有搜索到spa
本身断断续续一个月终于算是对倍增算法(就是一个名字 没必要纠结什么叫倍增算法)有个比较深刻理解3d
这是原始代码code
int wa[maxn],wb[maxn],wv[maxn],ws[maxn]; int cmp(int *r , int a, int b, int l) { return r[a] == r[b] && r[a+l] == r[b+l]; } void da (int *r , int *sa , int n, int m) { int i, j, p, *x = wa, *y = wb , *t; for(i = 0; i < m; i++) ws[i] = 0; for(i = 0; i < n; i++) ws[x[i] = r[i]]++; for(i = 1; i < m; i++) ws[i] += ws[i-1]; for(i = n-1; i >= 0; i--) sa[--ws[x[i]]] = i; for(j = 1,p = 1; p < n ; j <<= 1,m = p) { for(p = 0, i = n - j; i < n; i++) y[p++]=i; for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i] - j; for(i = 0; i < n; i++) wv[i] = x[y[i]]; for(i = 0; i < m; i++) ws[i] = 0; for(i = 0; i < n; i++) ws[wv[i]]++; for(i = 1; i < m; i++) ws[i] += ws[i-1]; for(i = n-1; i >= 0; i--) sa[--ws[wv[i]]] = y[i]; for(t = x,x = y,y = t,p = 1,x[sa[0]] = 0,i = 1; i < n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } }
要想了解上面的代码 首先你要知道什么叫基数排序(基数排序 百度百科)
htm
假设你也已经了解了了基数排序 那么下面咱们就要解析上面的代码
blog
还有在这里你首先要知道
什么是两个概念
后缀数组(SA[i]存放排名第i大的后缀首字符下标) 下面引号内内容能够不看
后缀数组 SA 是一个一维数组,它保存1..n 的某个排列 SA[1] ,SA[2] , ……, SA[n] ,而且保证Suffix(SA[i])<Suffix(SA[i+1]), 1 ≤ i<n 。也就是将 S 的 n 个后缀从小到大进行排序以后把排好序的后缀的开头位置顺次放入SA 中。
名次数组(rank[i]存放各个后缀的优先级)
名次数组 Rank[i] 保存的是 如下标 i 开头的后缀在全部后缀中从小到大排列的 “ 名次 ” 。
最后总结为 SA[i] = j表示为按照从小到大排名为i的后缀 是以j(下标)开头的后缀
rank[i] = j 表示为按照从小到大排名 以i为下标开始的后缀 排名为j
RANK表示你排第几 SA表示排第几的是谁
(记住这个就行)
下面的这张图就是上面算法的思想 可是我当时看的时候 晕了
下面咱们一步步来
首先 无论什么算法 咱们来点暴力的 假设如今 咱们直接来求一个字符串全部后缀的大小(所谓后缀的大小就是比较字符串的大小 这个必定要知道) 你会怎么作 ?
想到了!
两个for循环比较呗 可是这样算法确定慢
int smpStr(char* str,int len){ int k=0; for(int i=0;i<len;i++){ for(int j=i;j<len;j++){ if(strcmp(str+k,str+j)>0){ k = j; } } rank[k] = i; } }
考虑到后缀数组的特殊性 咱们换一种比较方式
为何称它特殊 由于一个字符串全部的后缀之间是有关系的
好比以字符串 abcdef 来举例
后缀bcdef 与 cdef 就有比较强的关系 后一个是前一个的组成部分 准确来讲是后半组成部分
那么怎么利用他们这种关系 请继续看
一首先考虑到比较方便咱们把全部的字母都减去 a-1 这里我只考虑全部字母都是小写字母的方式
加入字符串是 aabaaaab
下面将相邻俩个数合并为一个整数
这样下面使用基数排序对这个合并后的整数进行排序 为何使用基数排序 由于它的位数固定 也许你会问那
字母 ‘z’ 减去‘a’- 1 不是大于10了吗 那不是3位数了吗 不是这样的 把 z 减去‘a’- 1 =26 看作是一个数 而不是二十六
将至关于16进制 同样15不是看作两位数 而是用F来表示 固然你高兴 彻底能够把26写做Z之后 Z就是26
下面我讲解一下 这个很重要 为何要两两合并为一个数
首先求全部后缀数组最后组成为下图
那么每个后缀之间都是有重复的 第1个后缀的前两个就是第0个后缀的第一到第三个字母
那么一次类推 也就是说我按下图分为两两一组
将上图的两两一组一个整数按照基数排序的结果为
解释一下 第一个11 排第一名 第二个12 排第二名
那么你有没有发现第0个后缀到第7个后缀的前两个字母的比较已经出来了 由于第一个11 就是第1个后缀的前两个字母 第二个12 就是第2个后缀的前两个字母
什么意思 看图
好了 如今咱们已经比较全部后缀的前两个字母 下面我开始比较后面 那么我怎么比较前两个字母后面的字符串呢 由于刚才我已经把全部的两两字母的大小已经比较出来了 我如今能够利用下面的结果再比较 看图 其中合并后的 1121 就是第一个后缀的前四个字母 1211 就是第二个后缀的前四个字母
下面开始再次拼接 如图 最后这号拼成八位数 也就是正好字符串的长度 这时候可使用基数排序来比较 可是假如字符串10000个呢 那么有10000个后缀 每一个后缀的长度是10000 意味着最后拼接成的数也是有10000位 10000*10000咱们须要开辟这么大数据这是不可行的 那么咱们能不能将每次拼接的大数缩小呢?
先看图
首前后缀数组最终要得到的是后缀的排名 那么究竟是1112 仍是 11 是1221 仍是24 无所谓
我只要把他们保持合适的大小 就好比说 小明考了100分 小红考了89分 小刚考了55分
那么我如今把小刚设为0分 小红设为1分 小明设为2分 那么对他们最后的排名有影响吗 没有
小明仍是第一名 就是这个道理 这样咱们能够最大程度减少存储的开销
因此我只要每次对合并的数据进行按照从小到大排个序 用序号替换它 而后再次按照以前的步骤再次合并 再次排序替换 (何时结束)当所有的字符串都参与了比较就中止了
那么如今对1121 1211 2111 1111 1112 1120 1200 2000进行排序 分红两组 前两个字母一组后两个字母一组 好比 1121这四个数字 11 与 21 两份来基数排序
等等 你有没有发现 咱们上面的排序后的排名 跟第一关键字与第二关键字 有关系 也就是说
排名的大小就是第二关键字排名的 为何 由于排序后的排名就是 第二关键字的排序结果
那么与第一关键字有什么关系 ? 有没有发现 就是把第一关键字的11去掉 而后再加一个00
举个生动的例子 如今有不少人在排队 高矮不等 ,一开始是乱序的 如今保安要求 按从矮到高排列
排好序以后 你们都有了本身的位置 如今保安走开了 队伍又回到一开始的状态 而且原来站在最开始的人(乱序是的站在最开始的人)走了 来了一个小矮人 确定是最矮的 保安回来 要求再次排队 那么小矮人确定站在最前面 下面保安喊道 上次排序排第一的人接上 若是走的那我的是第一 那么就继续后面 若是不是上次排名第一的人就站上来 而后保安继续叫 一直到上次排名最后的一个
上面这个故事就对应于下面的代码
for(p = 0, i = n - j; i < n; i++)
y[p++]=i;
for(i = 0; i < n; i++)
if(sa[i] >= j)
y[p++] = sa[i] - j;