什麼叫後綴數組 首先要知道什麼叫後綴 ?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;