日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

后缀数组(讲解)

發布時間:2023/12/3 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 后缀数组(讲解) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

子串:從原串中選取連續的一段,即子串
空串也是子串
后綴:suf(k)為s(k…n)構成的子串
任何子串都是某個后綴的前綴
最長公共前綴 lcp(suf(i),suf(j))

問題:

將所有后綴suf(1),suf(2),suf(N)按照字典序從小到大排序

暴力sort N2 logN
二分+hash :
Nlog2N
cmp函數中二分suf(i)和suf(j)的lcp
return s[i+|lcp|] < s[j+|lcp|]
------- 以上為暴力做法
進入正文:
SA[1]排序第1的后綴的開始位置
Rank[i]=后綴suf(i)的排名
Rank[sa[l]] = l
sa[Rank[i]] = i
求sa然后得到rank

倍增
sub[i][k]:s從i開始長度為 2k的子串
sub[i][k] = s[i…i+(1<<k)-1 ],超過N的部分都視為’\0’(字典序最小符號)
rank[i][k]=sub[i][k]在長度2k的所有子串中的排名
sa[1][k] :在長度2k的所有子串中排名第1的子串的開始位置
step 1:先求sub[1][0],sub[2][0],…,sub[N][0]的字典排序
先求長度為1的子串,然后看字典序是多少
再求2,4,8,N,
當子串長度2k>=N時,子串排序就是后綴排序
利用rank[i…N][k],如何求出rank[1…N][k+1]
二分比較,
對于兩個子串sub[i][k+1]與sub[j][k+1]比較
先比較rank[i][k]與rank[j] [ k ] (先比前半部分)
如果相等再比較rank[i+2k]與rank [ j+2k ] (比較后半部分)
相當于二元組(第一關鍵字–>rank[i][k],第二–>rank[i+2k][k])排序


注意rank[i][k]值域是不超過N的正整數,可以用基數排序(桶排序)
基礎排序:先按second,再按照first
復雜度O(Nd) d是最大位數,此處d是2,(因為兩個關鍵詞)


寫SA時用cnt數組實現
將a[i]數組(1~N)基數排序,結果存放在sa數組中
sa[1]:排名第1th的數在a中的下標

for(int i=1;i<=N;i++)cnt[a[i]++;//桶排 for(int i=1;i<=N;i++)cnt[i]+=cnt[i-1];//求前綴和 for(int i=N;i;i--)sa[cnt[a[i]]--]=i;//sa[cnt[a[i]]]=i,cnt[a[i]]--;

a=[2,1,2,4,2]
cnt=[1,3,0,1,0]
cnt=[1,4,4,5,5]

大致過程:
for k = 1 ~ logN
按rank[i+2k][k]基數排序(第二關鍵字)
按照rank[i][k]基數排序,(第一關鍵字)
得到sa[i][k+1]數組
由sa[i][k+1]求出rank[i][k+1]
動畫鏈接
數據結構和算法動態可視化 (Chinese)
sa—>rank
rk[i]中有并列

for(p=0;i=1;i<=n;i++) {if(oldrk[sa[i]]==oldrk[sa[i-1]]&&oldrk[sa[i]+k]==oldrk[sa[i-1]+k])rk[sa[i]]=p;else rk[sa[i]]=++p; }




oi-wiki

代碼:

#include <algorithm> #include <cstdio> #include <cstring> #include <iostream>using namespace std;const int N = 1000010;char s[N]; int n, sa[N], rk[N << 1], oldrk[N << 1], id[N], cnt[N];int main() {int i, m, p, w;scanf("%s", s + 1);n = strlen(s + 1);m = max(n, 300);for (i = 1; i <= n; ++i) ++cnt[rk[i] = s[i]];for (i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];for (i = n; i >= 1; --i) sa[cnt[rk[i]]--] = i;//基數排序for (w = 1; w < n; w <<= 1) {//倍增memset(cnt, 0, sizeof(cnt));for (i = 1; i <= n; ++i) id[i] = sa[i];for (i = 1; i <= n; ++i) ++cnt[rk[id[i] + w]];for (i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];for (i = n; i >= 1; --i) sa[cnt[rk[id[i] + w]]--] = id[i];//上面為第二部分基數排序,下面為第二部分基數排序memset(cnt, 0, sizeof(cnt));for (i = 1; i <= n; ++i) id[i] = sa[i];for (i = 1; i <= n; ++i) ++cnt[rk[id[i]]];for (i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];for (i = n; i >= 1; --i) sa[cnt[rk[id[i]]]--] = id[i];memcpy(oldrk, rk, sizeof(rk));for (p = 0, i = 1; i <= n; ++i) {if (oldrk[sa[i]] == oldrk[sa[i - 1]] &&oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]) {rk[sa[i]] = p;} else {rk[sa[i]] = ++p;}//由sa得到新的rank數組}}for (i = 1; i <= n; ++i) printf("%d ", sa[i]);return 0; }

這個代碼會超時,經過優化后:

#include <algorithm> #include <cstdio> #include <cstring> #include <iostream>using namespace std;const int N = 1000010;char s[N]; int n, sa[N], rk[N], oldrk[N << 1], id[N], px[N], cnt[N]; // px[i] = rk[id[i]](用于排序的數組所以叫 px)bool cmp(int x, int y, int w) {return oldrk[x] == oldrk[y] && oldrk[x + w] == oldrk[y + w]; }int main() {int i, m = 300, p, w;scanf("%s", s + 1);n = strlen(s + 1);for (i = 1; i <= n; ++i) ++cnt[rk[i] = s[i]];for (i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];for (i = n; i >= 1; --i) sa[cnt[rk[i]]--] = i;for (w = 1; w < n; w <<= 1, m = p) { // m=p 就是優化計數排序值域for (p = 0, i = n; i > n - w; --i) id[++p] = i;for (i = 1; i <= n; ++i)if (sa[i] > w) id[++p] = sa[i] - w;memset(cnt, 0, sizeof(cnt));for (i = 1; i <= n; ++i) ++cnt[px[i] = rk[id[i]]];for (i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];for (i = n; i >= 1; --i) sa[cnt[px[i]]--] = id[i];memcpy(oldrk, rk, sizeof(rk));for (p = 0, i = 1; i <= n; ++i)rk[sa[i]] = cmp(sa[i], sa[i - 1], w) ? p : ++p;}for (i = 1; i <= n; ++i) printf("%d ", sa[i]);return 0; }

要求全文背誦
復雜度為O(n logn)

總結

以上是生活随笔為你收集整理的后缀数组(讲解)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。