body, table{font-family: 微软雅黑; font-size: 13.5pt}
table{border-collapse: collapse; border: solid gray; border-width: 2px 0 2px 0;}
th{border: 1px solid gray; padding: 4px; background-color: #DDD;}
td{border: 1px solid gray; padding: 4px;}
tr:nth-child(2n){background-color: #f8f8f8;}

后缀树:Trie树只适合前缀匹配和全字匹配,并不适合后缀和子串匹配。而后缀树(Suffix Tree)在这方面则非常适合。它与Trie树最大不同在于,后缀树的单词集合是由指定字符串的后缀子串构成。
eg: BIBS.
构造的后缀树如右图所示:
对于每个结点,包含两个数据成员,该结点出发可以到达的其他结点的指针(map)、从根结点到子孙结点的路径构成的子串在源字符串中的开始位置(vector)。
map<char,结点指针>、vector<int>


右图中包含4个子串,BIBS,BS,IBS,S。
BIBS中vector有两个数据0和2,因为BIBS和BS在源字符串中的位置是0和2.char==B,map有两个数据成员,一个为I和指向I的指针,另一个为S和指向S的指针。

#include<iostream>
#include<vector>
#include<map>
#include<string>
using namespace std;
class SuffixTreeNode  //后缀树的结点类
{
public:
        SuffixTreeNode() { }
        void insertString(string s,int idx);  //往后缀树中插入一个字符串
        vector<int> getIndex(string s);  //获取字符串在源字符串中出现的位置
        ~SuffixTreeNode();
private:
        map<char,SuffixTreeNode*> subTreeNode;  //结点出发可以到达的下一个字符以及指向该结点的指针
        vector<int> index;  //存放子字符串在S中的起始位置
        //可能有多个子串,或者从一个结点出发可以找到多个不同子串
};
void SuffixTreeNode::insertString(string s,int idx)
{//建立后缀树
        index.push_back(idx);  //字符串的起始位置保存.放到这里的原因,从root结点开始,等到字后一个字符的时候Str已被耗尽,也就是map没有元素了,但是这个结点index却要保存
        if(s.length()>0)  //如果字符串还没有耗尽,递归接着向下构造后缀树
        {
                char value = s[0];  //结点value的值解释字符串的第一个字符
                //判断该字符在不在该结点的map中,在就继续向下插入下一个字符,不在就新建一个结点并在当前结点的map中保存相关信息
                SuffixTreeNode* next = NULL;
                if(subTreeNode.find(value)!=subTreeNode.end())  //如果结点中存在该字符,next指针指向该结点
                {
                        next = subTreeNode.find(value)->second;
                }
                else  //没有该字符,就新建一个结点,并在当前结点的map中保存这个字符值,以及到这个字符的指针
                {
                        SuffixTreeNode* node = new SuffixTreeNode;
                        subTreeNode.insert(::make_pair(value,node));
                        next = node;
                }
                //递归利用字符串中的下一个字符接着构造后缀树
                string subStr = s.substr(1);
                next->insertString(subStr,idx);  //这个子串的起始位置是idx,以后一直是这个,不会变
        }
}
vector<int> SuffixTreeNode::getIndex(string s)
{
        //挨个字符匹配,最后返回最后一个字符所指向的结点的vector
        if(""==s)  //成功匹配到最后返回该结点的index
                return index;
        else
        {
                if(subTreeNode.find(s[0])!=subTreeNode.end())
                        return ((subTreeNode.find(s[0]))->second)->getIndex(s.substr(1));  //接着向下匹配
                else
                {//找不到,直接返回空
                        return vector<int>(0);
                }
        }
}
SuffixTreeNode::~SuffixTreeNode()
{//挨个释放动态申请的结点内存
        for(auto it = subTreeNode.begin();it!=subTreeNode.end();++it)
        {
                delete it->second;  //这里是一个递归过程
                it->second = NULL;
        }
}
class SuffixTree
{
public:
        SuffixTree(string str);  //用字符串(源串)初始化一个后缀树
        vector<int> getIndex(string str);  //获取一个字符串在源串中出现的位置
        ~SuffixTree();
private:
        SuffixTreeNode* _root;
};
SuffixTree::SuffixTree(string str)
{
        _root = new SuffixTreeNode();
        for(int idx=0;idx!=str.length();++idx)
        {
                string subStr = str.substr(idx);
                _root->insertString(subStr,idx);
        }
}
vector<int> SuffixTree::getIndex(string str)
{
        return _root->getIndex(str);
}
SuffixTree::~SuffixTree()
{
        delete _root;
        _root = NULL;
}

int main(int argc,char** argv)
{
        string testString = "mississippi";
        string arr[4] = {"is","sip","hi","sis"};
        SuffixTree tree(testString);
        for(int idx=0;idx!=4;++idx)
        {
                vector<int> ans = tree.getIndex(arr[idx]);
                cout<<arr[idx]<<" ";
                if(0!=ans.size())
                {
                        for(int iidx=0;iidx!=ans.size();++iidx)
                        {
                                cout<<ans[iidx]<<" ";
                        }
                        cout<<endl;
                }
                else
                {
                        cout<<"not find!"<<endl;
                }
        }
        system("pause");
}
查询效率:很显然,在上面的算法中,匹配成功正好比较了len(查找单词长度)次字符,则查询效率为O(len).
  后缀树还可以用来找出字符串S的最长重复子串S1(比如abcdabcefda里abc同da都重复出现,而最长重复子串是abc,利用vector大小和字符串长度就O(len)就可以找到)、找出字符串S1和S2的最长公共子串(比如字符串acdfg同akdfc的最长公共子串df)、找出字符串S的最长回文串S1(XMADAMYX的最长回文子串是MADAM)。

Suffix树,后缀树的更多相关文章

  1. 字符串 --- KMP Eentend-Kmp 自动机 trie图 trie树 后缀树 后缀数组

    涉及到字符串的问题,无外乎这样一些算法和数据结构:自动机 KMP算法 Extend-KMP 后缀树 后缀数组 trie树 trie图及其应用.当然这些都是比较高级的数据结构和算法,而这里面最常用和最熟 ...

  2. 【Todo】字符串相关的各种算法,以及用到的各种数据结构,包括前缀树后缀树等各种树

    另开一文分析字符串相关的各种算法,以及用到的各种数据结构,包括前缀树后缀树等各种树. 先来一个汇总, 算法: 本文中提到的字符串匹配算法有:KMP, BM, Horspool, Sunday, BF, ...

  3. BZOJ 3879: SvT [虚树 后缀树]

    传送门 题意: 多次询问,给出一些后缀,求两两之间$LCP$之和 哈哈哈哈哈哈哈竟然$1A$了,刚才还在想如果写不好这道题下节数学就不上了,看来是上天让我上数学课啊 $Suffix\ Virtual\ ...

  4. 后缀树(suffix tree)

    参考: 从前缀树谈到后缀树 后缀树 Suffix Tree-后缀树 字典树(trie树).后缀树 一.前缀树 简述:又名单词查找树,tries树,一种多路树形结构,常用来操作字符串(但不限于字符串), ...

  5. [算法]从Trie树(字典树)谈到后缀树

    我是好文章的搬运工,原文来自博客园,博主July_,地址:http://www.cnblogs.com/v-July-v/archive/2011/10/22/2316412.html 从Trie树( ...

  6. 012-数据结构-树形结构-哈希树[hashtree]、字典树[trietree]、后缀树

    一.哈希树概述 1.1..其他树背景 二叉排序树,平衡二叉树,红黑树等二叉排序树.在大数据量时树高很深,我们不断向下找寻值时会比较很多次.二叉排序树自身是有顺序结构的,每个结点除最小结点和最大结点外都 ...

  7. Trie树(代码),后缀树(代码)

    Trie树系列 Trie字典树 压缩的Trie 后缀树Suffix tree 后缀树--ukkonen算法 Trie是通过对字符串进行预先处理,达到加快搜索速度的算法.即把文本中的字符串转换为树结构, ...

  8. 后缀树的建立-Ukkonen算法

    参考: Ukkonen算法讲解 Ukkonen算法动画 Ukkonen算法,以字符串abcabxabcd为例,先介绍一下运算过程,最后讨论一些我自己的理解. 需要维护以下三个变量: 当前扫描位置# 三 ...

  9. [模板] 后缀自动机&&后缀树

    后缀自动机 后缀自动机是一种确定性有限状态自动机, 它可以接收字符串\(s\)的所有后缀. 构造, 性质 翻译自毛子俄罗斯神仙的博客, 讲的很好 后缀自动机详解 - DZYO的博客 - CSDN博客 ...

  10. POJ2774 --后缀树解法

    POJ2774 Long Long Message --后缀树解法 原题链接 题意明确说明求两字符串的最长连续公共子串,可用字符串hash或者后缀数据结构来做 关于后缀树 后缀树的原理较为简单,但 \ ...

随机推荐

  1. mui 访问系统相册将图片显示到网页

    访问系统相返回值为一个对象,通过转换为字符串可以查看,path.files[0]为返回路径去除路径赋值到src 调用摄像头返回的相片的path为一个路径通过 plus.io.resolveLocalF ...

  2. Python全栈开发-Day8-Socket网络编程

    本节内容 断言 Socket构建框架 ftp构建框架 Socket粘包 Socket介绍 Socket参数介绍 基本Socket实例 通过Socket实现简单SSH SocketServer 支持多用 ...

  3. 猫眼电影爬取(三):requests+pyquery,并将数据存储到mysql数据库

    还是以猫眼电影为例,这次用pyquery库进行爬取 1.简单demo,看看如何使用pyquery提取信息,并将提取到的数据进行组合 # coding: utf-8 # author: hmk impo ...

  4. 雷林鹏分享:XML 总结 下一步学习什么呢?

    XML 总结 下一步学习什么呢? XML 总结 XML 可用于交换.共享和存储数据. XML 文档形成 树状结构,在"根"和"叶子"的分支机构开始的. XML ...

  5. LeetCode--389--找不同

    问题描述: 给定两个字符串 s 和 t,它们只包含小写字母. 字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母. 请找出在 t 中被添加的字母. 示例: 输入: s = "ab ...

  6. linux进程管理之优先级

    进程优先级 nice ==================================================================================== Linu ...

  7. Spring Batch 基本的批处理指导原则

    下面是一些关键的指导原则,可以在构批量处理解决方案可以参考: 请记住,通常皮脸处理体系结构将会影响在线应用的体系结构,同时反过来也是一样的.在你为批量任务和在线应用进行设计架构和环境的时候请尽可能的使 ...

  8. Confluence 6 内容在空间中是如何组织的

    你可以将空间考虑为一个容器,在这个容器中保持了有关你项目中所有重要的东西,包括小组,项目或者项目相关的工作.这些东西有很高的自主性,这表示的是每个空间都有自己的的页面,文件,评论以及 RSS 新闻源. ...

  9. mysql find_in_set()函数的使用

    mysql 中 find_in_set() 函数语法: FIND_IN_SET(str,strList) str 要查询的字符串 strList 字段名,参数以“,”分隔,如(1,2,6,8) 查询字 ...

  10. 日期在Linux与Windows下的区别

    最近遇到了这个问题,就是相同的代码在Windows与Linux下的日期转换不一致. 原因:时区问题,主要是操作系统与JVM中的时区不同导致的 在网上查了很多处理的方法:最后总结出一条简单粗暴的方法:原 ...