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;
    }

}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值