後綴樹
定義
後綴樹是一種數據結構,一個具有 m 個字符的字符串 S 的後綴樹 T,就是一個包含一個根節點的有向樹,該樹恰好帶有 m+1 個葉子(包含空字符),這些葉子被賦予從 0 到 m 的 標號。每一個內部節點,除了根節點以外,都至少有兩個子節點,而且每條邊都用 S 的一個 子串來標識。出自同一節點的任意兩條邊的標識不會以相同的字符開始。 後綴樹的關鍵特徵是:對於任何葉子 i,從根節點到該葉子所經歷的邊的所有標識串聯起來 後恰好拼出 S 的從 i 位置開始的後綴,即 S[i,…,m]。
基本要求:
- 對任意給定的字符串 S,建立其後綴樹;
- 查找一個字符串 S 是否包含子串T;
- 統計 S 中出現 T的次數;
- 找出 S 中最長的重複子串。所謂重複子串是指出現了兩次以上的子串;
- 分析以上各個算法的時間複雜性。
- 應用後綴樹,查找兩個字符串Q和R中最長的共有子串。分析時間複雜性並通過實驗
結果驗證。
實現
#include <iostream>
#include <stdio.h>
#include <string>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
const int MAXN = 5000;
const int ALPHABET_SIZE = 256;
const int oo = 1 << 25;
int thelocation = 0;
int pos;//用於定位字符串中的字符的位置,,也即爲外層的一個for循環
//後綴樹節點
struct node {
//備註 [start,end)
int start;//字符串首位
int end;//字符串末位
int slink;//後綴鏈表指針
int next[ALPHABET_SIZE];//孩子指針
int location;//後綴下標
int father;//僅僅在查找最大重複子串的時候適用,初始化爲0
//node() {
// location = -1;
//}
int edge_length() {
return min(end, pos + 1) - start;//返回表示邊的長度 //"??"
}
};
//聲明大數組
node tree[2 * MAXN];
//後綴樹
class suffixtree {
private:
int root;//後綴樹根節點
int last_added;//後綴樹上一次添加的節點,以便於增加後綴鏈表
int treesize;
int needSL;//當形成一個內部節點時,更新needSL,用於添加後綴鏈表
int Remainder;//計數器Remainder: 記錄我們在當前階段需要插入的葉子節點數目。
int active_node, active_e, active_len;//活動三元組
int termnum;//用於找某個節點的所有葉子節點的內部變量
void numofleaf(int node) {
if (tree[node].location != -1) {
termnum++;
return;
}
for (int i = 0; i < ALPHABET_SIZE; i++) {
if (tree[node].next[i])
numofleaf(tree[node].next[i]);
}
}
public:
char text[MAXN];
suffixtree(string s="abc") {
needSL = 0;
last_added = 0;
pos = -1;
Remainder = 0;
//初始化三元組
active_e = 0, active_len = 0;
root = active_node = new_node(-1, -1);
for (int i = 0; i < s.size(); i++) {
st_extend(s[i]);
}
treesize = s.size();
}
int new_node(int start, int end = oo) {
node nd;
nd.start = start;
nd.father = 0;
nd.end = end;
nd.slink = 0;
for (int i = 0; i < ALPHABET_SIZE; i++) {
nd.next[i] = 0;
}
//添加後綴下標
if (end == oo) {
nd.location = thelocation;
thelocation++;
}
else nd.location = -1;
tree[++last_added] = nd;
return last_added;
}
//active_e 是活躍的邊在text內的序號
char active_edge() {
return text[active_e];
}
//增加後綴鏈表 -> 當前內部節點(needSL)指向本階段最近一次建立的內部節點(int node)
void add_SL(int node) {
//加入後綴鏈表
if (needSL > 0)tree[needSL].slink = node;
needSL = node;
}
//更新活動三元組
bool walk_down(int node) {
if (active_len >= tree[node].edge_length()) {//判斷是否更新活躍點,
active_e += tree[node].edge_length();
active_len -= tree[node].edge_length();
active_node = node;
return true;
}
return false;
}
//往後綴樹中增加字符->c
void st_extend(char c) {
text[++pos] = c;//text是後綴樹組
needSL = 0;//重置內部節點(needSL) //新的階段
Remainder++;
while (Remainder > 0) {
if (active_len == 0)active_e = pos; //更新活躍邊
if (tree[active_node].next[active_edge()] == 0) { //? active_edge() 字符?
//從根節點創建葉子節點
int leaf = new_node(pos);
tree[active_node].next[active_edge()] = leaf;
add_SL(active_node);
}
else {
int nxt = tree[active_node].next[active_edge()];
if (walk_down(nxt)) continue; //observation 2 //沿着活躍邊往下走,並且可以越過一個點,即是否更新活躍點,當經過一個邊所有的字符時,更新活躍點
if (text[tree[nxt].start + active_len] == c) { //observation 1 新加入的字符,可以形成隱式樹
active_len++; //更新活躍邊長度
add_SL(active_node);//判斷是否需要添加後綴鏈表
break;
}
//從內部分裂--每次內部分裂都會形成一個葉子節點
int split = new_node(tree[nxt].start, tree[nxt].start + active_len);
tree[active_node].next[active_edge()] = split;
//構造葉子節點
int leaf = new_node(pos);
tree[split].next[c] = leaf;
tree[nxt].start += active_len;
tree[split].next[text[tree[nxt].start]] = nxt;
add_SL(split); //按照規則2更新三元組
}
Remainder--;
if (active_node == root && active_len > 0) { //按照規則1更新三元組
active_len--;
active_e = pos - Remainder + 1;
}
else
active_node = tree[active_node].slink > 0 ? tree[active_node].slink : root; //按照規則3更新三元組
}
}
//判斷是否存在子串T
bool search(string T) {
if (T.size() > treesize)return false;
//找到第一個結點
if (!tree[1].next[T[0]])return false;
int nxt = tree[1].next[T[0]];
for (int i = 0; i < T.size(); i++) {
for (int j = tree[nxt].start; j < tree[nxt].end; j++) {
if (text[j] == T[i]) {
i++;
//遍歷i結束
if (i == T.size())return true;
}
else return false;
}
//找到下一個節點
nxt = tree[nxt].next[T[i]];
if (!nxt) return false;
i--;//更新i
}
//return true;
}
//找到字符串出現的次數
int count(string T) {
if (T.size() > treesize)return 0;
//找到第一個結點
if (!tree[1].next[T[0]])return 0;
int nxt = tree[1].next[T[0]];
for (int i = 0; i < T.size(); i++) {
for (int j = tree[nxt].start; j < tree[nxt].end; j++) {
if (text[j] == T[i]) {
i++;
//遍歷i結束
if (i == T.size()) {
termnum = 0;
numofleaf(nxt);
return termnum;
}
}
else return 0;
}
//找到下一個節點
nxt = tree[nxt].next[T[i]];
if (!nxt) return 0;
i--;//更新i
}
}
//找到最長的重複子串
void longstring() {
queue<int> qq;
queue<int> ans;
qq.push(1);
while (!qq.empty()) {
int node = qq.front();
qq.pop();
cout << node-1 << " node:" << endl;
for (int i = 0; i < ALPHABET_SIZE; i++) {
if (tree[node].next[i] != 0 ) {
cout << tree[node].next[i]-1<<" " ;
termnum = 0;
numofleaf(tree[node].next[i]);
if (termnum < 2) continue;
qq.push(tree[node].next[i]);
tree[tree[node].next[i]].father = node;
ans.push(tree[node].next[i]);
}
}
cout << endl;
}
string ans1;
int num = 0;
int start1;
int end1;
int start2[256];
int end2[256];
start1 = 0;
end1 = 0;
while (!ans.empty()) {
int node = ans.front();
ans.pop();
start2[num] = tree[node].start;
end2[num] = tree[node].end;
while (tree[node].father != 1) {
node = tree[node].father;
start2[num] = tree[node].start;
}
if (end2[num] - start2[num] > end1 - start1) {
start1 = start2[num];
end1 = end2[num];
}
num++;
}
//輸出所有最長的重複子串
for (int i = 0; i < num; i++) {
if ((end2[i] - start2[i]) == (end1 - start1)) {
for (int j = start2[i]; j < end2[i]; j++) {
cout << text[j];
}
cout << endl;
}
}
}
};
int main() {
string S, T;
S = "abcabxnmhnmj$";
suffixtree t(S);
t.longstring();
while (1) {
cin >> T;
if (t.search(T)) {
cout << "YES";
}
else cout << "NO";
cout << endl;
cout << "number:" << t.count(T) << endl;
}
}