c++ 字符串数组长度排序_数组 | 后缀数组的求法及应用
作者:Andy__lee
鏈接:https://blog.nowcoder.net/n/6b4a93e186ed4a358321de6a7c3b4f19
來源:??途W
定義
維基百科 - 后綴數組
讓我們來看一下 wiki 上的定義:
在計算機科學里, 后綴數組(英語:suffix array)是一個通過對字符串的所有后綴經過排序后得到的數組。此數據結構被運用于全文索引、數據壓縮算法、以及生物信息學。
令字符串
表示 的子字符串,下標從 到 。 的后綴數組 被定義為一個數組,內容是 的所有后綴經過字典排序后的起始下標。滿足
。求法
大體思想
這玩意可以使用倍增來求,當然也有常數比較大的
算法。中心思想就是假設我們求出了考慮每個后綴前
個的字典序,現在我們需要擴展到 的情形。首先 的時候十分簡單,就是對應的 ASCII 碼??紤]每次 ,相當于要把每個后綴當做一個二元組來排序。我們考慮對于每個后綴指定兩個變量 和 ,目前考慮的長度下, 指這個后綴的排名, 指這個后綴后 個字母的排名。顯然 可以由上一輪的 得到(后面會單獨說)。然后我們就可以用上一輪的 和 當做二元組來排序,就能更新出這次的 了。相信大家看完后肯定是一臉 mb,接下來我來詳細的說一下算法過程。
算法過程
首先我們來定義一些變量:
表示排名為 的后綴的位置, 表示第 個后綴的排名, 表示每次倍增里的第二關鍵字排名為 的位置,我們設這個字符串 的長度為 。首先按照定義,顯然有
也就是說明了這兩個數組可以互相
推。開始時
我們拿來排序的二元組是 來排序,顯然 ASCII 碼小的會在前面,相同的靠前的會在前面。然后我們開始倍增。考慮已經求出了長度為 的答案,考慮去更新 的答案。現在我們考慮如何求出
(第二關鍵字)。代碼如下:p = 0; FOR(i,1,w) tp[++p] = N-w+i; FOR(i,1,N) if(sa[i] > w) tp[++p] = sa[i]-w;首先對于長度
的后綴,一定字典序會在前面,我們先都拿出來。然后對于長度
的后綴,我們發現后綴 的后 個字符就是上一輪后綴 的前 個字符。(考慮這一輪 個字符的意義下)所以代碼就是按照上一輪的順序從小到大枚舉了每個長度
的后綴,然后找到第二關鍵字的對應位置定位過去即可。之后我們搞個比較高效的排序(基數排序)排一下。
然后因為在這個倍增過程中可能有的后綴的
暫時相同,但是最后的答案一定是互不相同,于是我們需要對于這一輪的結果去一下重(重新分配 編號)std::swap(tp,rk); // rk 沒用了,當然這里最好寫指針交換 rk[sa[1]] = p = 1; FOR(i,2,N) rk[sa[i]] = (tp[sa[i-1]] == tp[sa[i]] && tp[sa[i-1]+w] == tp[sa[i]+w]) ? p : ++p;去重的原理和上面的類似:如果前半段和后半段在上一輪的rkrk相同的話那它們當前就是相同的。
然后就做完了。。。。
現在我們要解決的是找到一種高效的排序方法,首先顯然你不能用
排序,要不然和暴力就沒區別了(可能還更慢)。所以我們考慮
的基數排序。先放一下代碼:
inline void sort(){FOR(i,0,M) tax[i] = 0;FOR(i,1,N) tax[rk[i]]++;FOR(i,1,M) tax[i] += tax[i-1];ROF(i,N,1) sa[tax[rk[tp[i]]]--] = tp[i]; }首先我們把桶清空,然后統計每種
的出現次數,然后做個前綴和(有助于快速查詢排名)。然后我們重點關注最后一句是在干什么。首先我們按第二關鍵字從大到小枚舉(
),然后我們在找一下當前枚舉的這個后綴的第一關鍵字排名( ??),然后由于是第二關鍵字從大到小枚舉,所以目前的字符串排名一定是前半段相同的后綴中(當前 相同)最靠后的一個后綴,所以我們的前綴和就派上用場了。最后直接更新一下就可以了(排名是 的后綴是 號后綴)。是不是很好理解?這樣后綴數組就寫完了。
附上Luogu - 后綴排序的代碼:
#include <algorithm> #include <iostream> #include <cstring> #include <climits> #include <cstdio> #include <vector> #include <cstdlib> #include <ctime> #include <cmath> #include <queue> #include <stack> #include <map> #include <set>#define Re register #define LL long long #define U unsigned #define FOR(i,a,b) for(Re int i = a;i <= b;++i) #define ROF(i,a,b) for(Re int i = a;i >= b;--i) #define SFOR(i,a,b,c) for(Re int i = a;i <= b;i+=c) #define SROF(i,a,b,c) for(Re int i = a;i >= b;i-=c) #define CLR(i,a) memset(i,a,sizeof(i)) #define BR printf("--------------------n") #define DEBUG(x) std::cerr << #x << '=' << x << std::endlconst int MAXN = 1000000+5;char str[MAXN]; int N,sa[MAXN],tax[MAXN],M; int pool1[MAXN],*rk = pool1,pool2[MAXN],*tp = pool2;inline void sort(){FOR(i,0,M) tax[i] = 0;FOR(i,1,N) tax[rk[i]]++;FOR(i,1,M) tax[i] += tax[i-1];ROF(i,N,1) sa[tax[rk[tp[i]]]--] = tp[i]; }inline void SuffixSort(){M = 75;FOR(i,1,N) rk[i] = str[i]-'0'+1,tp[i] = i;sort();for(int w = 1,p = 0;p < N;w <<= 1,M = p){p = 0;FOR(i,1,w) tp[++p] = N-w+i;FOR(i,1,N) if(sa[i] > w) tp[++p] = sa[i]-w;sort();std::swap(tp,rk);rk[sa[1]] = p = 1;FOR(i,2,N) rk[sa[i]] = (tp[sa[i-1]] == tp[sa[i]] && tp[sa[i-1]+w] == tp[sa[i]+w]) ? p : ++p;}FOR(i,1,N) printf("%d ",sa[i]); }int main(){scanf("%s",str+1);N = strlen(str+1);SuffixSort();return 0; }Height 數組
后綴數組如果只能排序的話那貌似沒什么用,大多數后綴數組題目主要還是考察 Height 數組的性質。
首先扔一些定義。。。
表示 號后綴, 表示后綴 和 的最長公共前綴。同時我們繼續沿用上文的 等定義。定義
如果直接枚舉后綴那還求后綴數組干嘛,所以說我們要考慮能否通過后綴數組求出的信息來線性推出
。對于
數組有性質 。證明就不證了(筆者太菜不會,其實分類討論一下就可以了)。代碼:
inline void get(){int j,k = 0;FOR(i,1,N){if(k) --k;int j = sa[rk[i]-1];while(str[i+k] == str[j+k]) ++k;height[rk[i]] = k;} }經典應用
求任意后綴的最大 lcp
這東西隨便維護一個區間極值就可以了,
查詢。可重疊最長重復子串
意思是求最長的子串使得在字符串中重復出現過。
根據定義就是 height 數組中的最大值
不可重疊最長重復子串
POJ - 1743
我們來考慮二分答案。假設現在我們二分的答案是
然后考慮在一些地方將 height 分開,保證每一段最小
。分完組之后枚舉一下看看長度是否滿足存在不交的重復子串就可以了。本質不同的子串數量
注意到子串 = 后綴的前綴。
對于排名為
的后綴,它有 個前綴,但是有 個是和排名為 的后綴的前綴相同的,減去就可以了。所以答案就是
查看作者更多博客:https://blog.nowcoder.net/rainair
總結
以上是生活随笔為你收集整理的c++ 字符串数组长度排序_数组 | 后缀数组的求法及应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 语言生日创意代码_你生日那天的宇宙什么样
- 下一篇: java计算二叉树的节点最小值_java