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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

算法学习:后缀数组(SA)

發布時間:2025/7/14 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 算法学习:后缀数组(SA) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

【參考博客】

https://xminh.github.io/2018/02/27/%E5%90%8E%E7%BC%80%E6%95%B0%E7%BB%84-%E6%9C%80%E8%AF%A6%E7%BB%86(maybe)%E8%AE%B2%E8%A7%A3.html

?

?


?

【定義】

?

【后綴】從第i位到字符串結尾的子串

??


【解決問題】

?

從而解決

...................在字符串中找子串

...................比較子串關系

...................查找不同子串的數目

?

一般來說都是解決

字符串和子串關系的問題

?


?

【算法學習】

?

后綴數組能夠在 nlogn的時間復雜度內求取出以下數組

?

【注意】明白字符串包含字符的范圍?

?

SA [] 儲存,第 i 個數字表示的是字典序第 i 大的后綴是以 SA_i 開始的后綴

即? ?“排第幾的是哪個后綴

rank [] 儲存,從第i位開始的后綴的字典序排名是 i

即? ?“某個后綴排第幾個”?

?

對SA的求取,我們可以看作對所有的后綴進行排序

而這個排序顯然如果直接莽的話肯定T,所以我們需要另外一種方法

?

這里使用基數排序+倍增的方法進行優化

?

將所有的后綴進行排序得到SA,這是我們的目的

?

基數排序,是對兩個關鍵字的元素進行排序從而達到線性復雜度的方法

顯然,在當兩個后綴第一個字符相等的情況下,我們不可避免的去用第二個字符進行比較

這里我們需要注意到一個事情

第 i 個后綴的第二個字符是第 i + 1 的第一個字符

那這和直接莽好像沒有什么區別?

?

?

所以我們就用到了倍增,每次確定后綴的以前 2 ^ k 長度的字符串排序的順序

然后通過這個,就能夠求出 2 ^ ( k + 1 ) 長度的后綴的前綴

通過,第 i 位的和第 i + k 位的,于是就能求出來

?

因為第一個字符有可能一樣,導致有兩個后綴排名相等

所以總排名數和后綴長度不同

所以長度為2時同理

而當所有總排名和后綴長度相同時,這個時候就找到了所有

?

下面是對使用到的各個數組的意義的描述:

c[] 桶,記錄第 i 位的元素有多少個

x[] 后綴 i 的第一關鍵字,所以最開始是等于第 i 位字符

y[] 第二關鍵字排名第 i 的字符串,第一關鍵字的位置

?

【代碼】

?首先求出所需要的幾個數組的初值

//初始化int n = strlen(s+1);int m = 128;//m只需要大于 ascii(‘z')即可//因為第一步并不知道桶的規模for (int i = 1; i <= n; i++)++c[x[i] = s[i]];//計算每一個字符的數量,同樣的放在一起for (int i = 2; i <= m; i++)c[i] += c[i - 1];//求前綴和for (int i = n; i >= 1; i--) SA[c[x[i]]--] = i;//從后往前,這樣就能求出最開始順序

倍增的同時進行排序

for (int k = 1; k <= n; k <<= 1) //k是之前提到的,每次枚舉的字符串長度 {int num = 0;for (int i = n - k + 1; i <= n; i++) y[++num] = i;//先將最后的幾個去掉for (int i = 1; i <= n; i++)if (SA[i] > k)y[++num] = SA[i] - k;//當這個位置的后綴在長度之外時//將這個位置的字符放到第二關鍵字中for (int i = 1; i <= m; i++)c[i] = 0;//清空桶for (int i = 1; i <= n; i++)c[x[i]]++;//放入第一關鍵字//第一關鍵字是已經算好的//第一次是初始化時計算好的//第二次的計算過程在下面for (int i = 2; i <= m; i++)c[i] += c[i - 1];//和初始化一樣的求前綴和for (int i = n; i >= 1; i--)SA[c[x[y[i]]]--] = y[i], y[i] = 0;//在保證第一關鍵字的同時使用第二關鍵字排序//從后往前,第二關鍵字靠后的會被先剔除掉//考慮下數組的操作 swap(x, y);//這里是為了保存上一步得到的y,沒有太多其他意思x[SA[1]] = 1; num = 1;for (int i = 2; i <= n; i++)x[SA[i]] =(y[SA[i]] == y[SA[i - 1]] && y[SA[i] + k] == y[SA[i - 1] + k]) ?//這里注意回憶SA的意義//比較的是和其排名相近的元素,也是最有可能兩個關鍵字都相同的元素//當字符的第二關鍵字,前后都相同時//說明沒有新的元素,所以不加//如果有,則加 ? num : ++num;if (num == n) break;m = num;}

?


?

【模板題】

?

【luogu P3809】

求一個字符串的SA

?

#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int MAXN = 1000010; char s[MAXN]; int SA[MAXN]; int x[MAXN], c[MAXN], y[MAXN]; void get_SA(char *s) {//初始化int n = strlen(s + 1);int m = 128;for (int i = 1; i <= n; i++)++c[x[i] = s[i]];//計算每一個字符的數量,同樣的放在一起for (int i = 2; i <= m; i++)c[i] += c[i - 1];//求前綴和for (int i = n; i >= 1; i--)SA[c[x[i]]--] = i;//從后往前,這樣就能求出最開始順序for (int k = 1; k <= n; k <<= 1){int num = 0;for (int i = n - k + 1; i <= n; i++) y[++num] = i;//把最后幾個字符放進去for (int i = 1; i <= n; i++)//按照順序遍歷if (SA[i] > k)//如果長度大于ky[++num] = SA[i] - k;//把第一個放進去for (int i = 1; i <= m; i++)c[i] = 0;for (int i = 1; i <= n; i++)c[x[i]]++;//x[i]是第一關鍵字for (int i = 2; i <= m; i++)c[i] += c[i - 1];//求前綴和for (int i = n; i >= 1; i--)SA[c[x[y[i]]]--] = y[i], y[i] = 0;swap(x, y);x[SA[1]] = 1; num = 1;for (int i = 2; i <= n; i++)x[SA[i]] = (y[SA[i]] == y[SA[i - 1]] && y[SA[i] + k] == y[SA[i - 1] + k]) ? num : ++num;if (num == n) break;m = num;}return; } int main() {scanf("%s", s + 1);get_SA(s);int n = strlen(s + 1);for (int i = 1; i <= n; i++)printf("%d ", SA[i]);return 0; } View Code

?

?


【擴展】

?

LCP/height數組的求取

?

轉載于:https://www.cnblogs.com/rentu/p/11331536.html

總結

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

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