UKK算法構建後綴樹

後綴樹

詳情

定義

後綴樹是一種數據結構,一個具有 m 個字符的字符串 S 的後綴樹 T,就是一個包含一個根節點的有向樹,該樹恰好帶有 m+1 個葉子(包含空字符),這些葉子被賦予從 0 到 m 的 標號。每一個內部節點,除了根節點以外,都至少有兩個子節點,而且每條邊都用 S 的一個 子串來標識。出自同一節點的任意兩條邊的標識不會以相同的字符開始。 後綴樹的關鍵特徵是:對於任何葉子 i,從根節點到該葉子所經歷的邊的所有標識串聯起來 後恰好拼出 S 的從 i 位置開始的後綴,即 S[i,…,m]。

基本要求:

  1. 對任意給定的字符串 S,建立其後綴樹;
  2. 查找一個字符串 S 是否包含子串T;
  3. 統計 S 中出現 T的次數;
  4. 找出 S 中最長的重複子串。所謂重複子串是指出現了兩次以上的子串;
  5. 分析以上各個算法的時間複雜性。
  6. 應用後綴樹,查找兩個字符串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;
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章