算法学习:后缀数组(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)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 算法学习:BSGS
- 下一篇: 【HDU 4511】小明系列故事——女友