McCreight-Algorithmus

在O(n)时间内构造后缀树

基本思想

在ST中插入 suffi suff_i时,以下内容可以起到帮助作用:
1.v是ST中从根节点到叶节点i-1的一个内部节点,那么path-label(v)=ca就是 suffi suff_i的前缀。(其中c是单个字符,a是一个字符串并且a可能为空)
2.因为v是一个内部节点,故存在其他后缀 suffj suff_j(j < i-1)拥有相同的前缀。//不一定比i-1小吧???也不一定存在把???
//因为节点存在所以一点存在j,节点意味着分叉
3.因为 suffj+1 suff_{j+1}已经插入,那么同样的也就存在以a为前缀的路径。当这个前缀a在内部节点u处终结,那么我们就有SL(v) = u。现在就可以从节点v直接跳到节点u了。
4.如此就可以避免比较 suffi suff_i的前|a|个字符
定理:(很重要)
如果v是在ST中插入后缀 suffi suff_i时建立的内部节点,并且有path-label(v) = ca,c是单个字符,a是一个字符串(可能为空)。那么或者存在一个节点u,相应的path-label(u) = a, 又或者他将会在插入后缀 suffi suff_i时被创造。
(有这个定理也可以看出为什么,你没有那么你爸也一定会有)
//插靓图。。。
//顺便插入设定SL的方法图

伪代码

//Algorithmus SuffiBaumAusStringSchnell(s):Knoten root = new Knoten();
Knoten leaf = new Knoten(1);//Knoten für ganzen String bzw. suff_i
Kante edge = new Kante (root, leaf, suff_i);--Kante von der Wurzel zu erstem Blatt
int i = 2; //aktuelles Suffixwhile(x <= |s|){
//从叶节点出发向上查询,直到第一个节点
<steige von leaf auf bis zum ersten internen Knoten v>
//如果v有后缀指引
if(<v hat Suffix-Verweis SL(v)>{//跟随指引到达节点u<Folge SL(v) zu Knoten u>
}else{//继续上升到v的父节点v',并跟随v'的指引到达u'<steige auf zu Vaterknoten v' von v und folge SL(v') zu Knoten u'>//沿着u'向下越过|path-label(v)|-|path-label(v')|个字符建立新的节点u SL(v)= u;<Steige von u' um |path-label(v)|-|path-label(v')| Zeichen in dem Baum ab und erzeuge Knoten u durch Spalten einer Kante>
}
//沿着u向下走,直到出现不同字符
<Steige von u in den Baum ab, so lange die bisher durchschrittene Pfad-Beschriftung mit suff_i übereinstimmt>
//保存最大相同前缀的字符数为m
<Benenne Anzahl der übereinstimmenden Zeichen mit m>
Knoten leafParent;
//如果现在刚好在节点上
if(<Abstieg endet an Knoten>){//把目前的点作物叶的父节点leafParent = <Knoten, an dem Abstieg endet>;
}else{// abstieg endet mitten in KanteleafParent = new Knoten();//插入新节点作为叶的父节点<spalte zuletzt durchschrittene Kante durch Einfügen von leafParent>
}
leaf = new Knoten(i); //Knoten i als Beschriftung
edge = new Kante(leafParent, leaf, s[i+m...|s|);
p = p + 1;
}

//上面的算法缺少设定SL(v),地方比较明显,但是怕出错,因此源代码不好改。知道就好
//上面算法也没有撞天花板检测,知道就行。。。好吧知道。。思路就行
//向上最多爬两层,必定有LS的。这个由定理可以推出
//没插入一个值耗时为整数,固最后总时为O(n)
//自己玩玩,画123456123456y的后缀树??
//1234x1234yc1234xc1234z的后缀树呢??

后缀树的数据结构(Datenstruktur Suffix-Braum)

后缀树的节点一般有两种数据结构:
1.Knoten1
Knoten1[] children = new Knoten1[a];
也就是给每个孩子分配一个指针
每个节点占O(a),整棵树占O(n*a),寻找孩子耗时O(1)
2.Knoten2
Knoten2 leftMostChild;
Knoten2 nextSibling;
只有最左边孩子和邻近兄弟的指针
每个节点占O(1),整棵树占O(n),寻找孩子耗时O(a)

后缀树以及后缀数组的应用

比较经典的用处有三个,分别是:
1.Pattern Matching
2.Text-Kompression
3.Longest Common Prefix

Pattern Matching

1.已知P是一个Pattern,ST(s)是字符串s的后缀树。
2.对照Pattern,从ST的根节点出发向下走,
3.如果走到无路可走了,就输出匹配不了
4.如果在一条边上匹配结束,那么直接跳到该边的终点。如果匹配结束时干好在一个节点上,那就省了上面那一步。
5.在一这个节点为根节点的子树种的所有叶节点都符合这个Pattern。
(Anfrage kann auch Intervall sein. Nach Intervallgrenzen suchen)//啥意思???
正则表达式:
同样的方法也适合正则表达式( reguläreer Ausdruck):
1.首先要把正则表达式转化为有限状态自动机(话说无限的可不可以???)(Automaten)
2.纵向搜说自动机(Tiefsuche),同时ST树从树根向下匹配
3.自动机的回路同样对应着树的向下搜索。
4.当自动机达到目标终点:处理同前

//Algorithmus FindeAlleMatches(ST(s),P)
//后缀树根节点
Knoten node = <Wurzel des Suffix-Baums
Kante edge;
int p = 1;//aktuelle Position in P
int e = 1;//Aktuelle Position in aktueller Kante
while(p <= |p|){
if(node != null){//拥有与p匹配的字符串的边edge = <von node ausgehend Kante, deren Beschriftung mit p[p] beginnt>;//没找到这样的边if(edge == null){//keine passend Kante gefunden//返回没找到return keine Matches von P gefunden>;//第一个字符已经用于选边了}else{//erstes Zeichen schon für Auswahl  der Kante geprüft//直接看边上字符串的第二个字符e = 2;//geht zum nächsten Zeichen der Beschriftung von edgep = p + 1;}node = null;
}else if(edge != null){//e大于边上字符串的长度if( e > |<Beschriftung edge>|){//node设为边的终点node = <Endknoten von edge>;edge = null;//如果边上字符串的第e个字符和P[p]匹配}else if (<Beschriftung edge>[e] == P[p]){e = e + 1;p = p + 1;}
}else {//返回找不到对应匹配return <keine Mathes von P gefunden>
}
}
if(edge != null){//把node设为边的终点node = <Endknoten von edge>
}
if( node != null){//返回node下得所有叶节点return <alle Blätter im Teilbaum unter node》
}else{//返回没找到P的匹配return <keine Matches von P gefunden>
}

Pattern Matching和后缀数组
基本思想:因为后缀数组是按照字典序排序的,因此匹配项在数组中的位置应该也是直接相连的。//??????
1.找到满足下面条件SA(s)中的最小得位置i:P是 suffSA[i] suff_{SA[i]}的前缀。
2.如果不存在这样的i,那么P不在s中
3.如果存在,则寻找SA[s]满足下面条件的最大得位置j:P是 suffSA[j] suff_{SA[j]}的前缀
4.SA[i..j]就是匹配Pattern的项
5.时间:O(|P|*log|s|)
其中log|s|用于前缀二进制匹配。

//Algorithmus FindeAlleMatches(SA(s), P):int left = 1; //linker Rand der binären Suche
int right = |s|; //rechter Rand der binären Suche
//m是指中间位置binary search
int m; // mittlere Position der binären Suche//(suff_SA[l]不以P开始或suff_SA[r]不以P开始)&&(1<r)
while((!<suff_SA[l] beginnt mit P>||!<suff_SA[r]beginnt mit P)&&(l<r)){m = (1+r) / 2;if(suff_SA[m] < P){l = m;}else if(suff_SA[m] > P){r = m;}else{int i = m;//邻近检测while ((i>1)&&<suff_SA[i-1] beginnt mit P>)i++;int j = m;while((j < r)&& <suff_SA[j+1] beginnt mit P)j++;//返回匹配项return <Positionen der Matches in SA(s) zwischen i und j;}
}
if(l < r){//返回匹配项return <Positionen der Matches in SA(s) zwischen l und r>;
}else{//返回找不到匹配return <P kommt in s nicht vor>;
}

另外利用LCP数组可以减少字符比较,最终可以把时间缩短到O(|P|+log|s|)。//??

Text Compression

Ziv-Lempel-Algorithmus:
基本思想:使用输入字符串的冗余(redundanz)

//Algorithmus Komprimiere(s)
//从第一个字符开始,k为压缩后的字符串
String k = s[1];//beginne komprimierten String mit erstem Zeichen
int i = 2;//Position im verbleibenden Teil von swhile(i <= |s|){//solange weitere Zeichen von s zu komprimieren//l赋值为s[1...i-1]中含有的suff_i的最长前缀int l = |<längstes in s[1...i-1] enthaltene Präfix von suff_i>|;if(l==0){//不压缩k += s[i];//hänge aktuelles Zeichen an komprimierten String an i++;//gehe yum nächsten Zeichen}else{//nächste 1 Zeichen komprimierbar//j设置为s[i...i+l-1]在s[1...i-1]中的位置int j = <Position von s [i...i+l-1] in s[1...i-1]>;k += (j,l);//hänge j und l an komprimierten String ani++;}}

//可插图
为了方便压缩,一般在使用之前要做一些准备:
1.在Knoten中存储String-depth
2.在每个内部节点中存储min(v),他用于表示v下最小得叶节点的编号。
//加入这些信息后能怎么优化呢?????
//插图吧、、、、

Longest Common Prefix(Tiefster gemeinsamer Vorgänger,Lowest Common Ancestor)

Euler Tour:
就是用数组E记录纵向遍历经过的每一个节点。具体看图吧
//插图一张 如果能上传的话
叶节点出现一次,内部节点出现多次,以根节点为起始,同时也以根节点为终结。
准备:
1.首先给ST上得节点编号,包括内部节点和根节点
2.用数组E记录Euler Tour:|E|=2|ST|-1
//出根节点外,每个内部节点重复的次数为其出度个数+1
//内部节点出度总和为树节点树-1
3.用数组L记录E访问到的节点的tree-depth:|L|=2|ST|-1
L[i]=tree-depth(E[i])
4.用数组R记录节点i在E中第一次出现的位置:|R|=|ST|
R[i]= rankE(i) rank_E(i)
5.那么 RMQL(i,j) RMQ_L(i,j)为索引i和j之间最小的L(包括i,j)
RMQ为Range Minimum Query
那么就有lca(i,j) = E[RMQL(R[i],R[j])] E[RMQ_L(R[i],R[j])]
//插图一张
优化:
//木看懂 回看、、、、

数据库应用-后缀树及后缀数组(Suffix-BäumeSuffix-Arraz)-2相关推荐

  1. 后缀树和后缀数组的一些资料收集

    后缀树(Suffix tree)是一种数据结构,能快速解决很多关于字符串的问题.后缀树的概念最早由Weiner 于1973年提出,既而由McCreight 在1976年和Ukkonen在1992年和1 ...

  2. 字符串相关处理kmp,前缀数,后缀树,后缀数组,最长回文串,最长重复字串,最长非重复字串

    1. 最长回文串 一般用后缀数组或者后缀树可以解决, 用此方法:http://blog.csdn.net/v_july_v/article/details/6897097 预处理后缀树,使得查询LCA ...

  3. 字符串-后缀树和后缀数组详解

    文章目录 后缀树 后缀数组 概念 sa[] rk[] height[] 例题 HDU-1403最长公共子串 洛谷P2408 不同子串个数 HDU-5769Substring 后缀树 建议先了解一下字典 ...

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

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

  5. 【转】从Trie树(字典树)谈到后缀树

    本文第一部分,咱们就来了解这个Trie树,然后自然而然过渡到第二部分.后缀树,接着进入第三部分.详细阐述后缀树的构造方法-Ukkonen. 第一部分.Trie树 1.1.什么是Trie树 Trie树, ...

  6. 从Trie树(字典树)和后缀树

    从Trie树(字典树)谈到后缀树 转载:http://blog.csdn.net/v_july_v/article/details/6897097#t22 感谢作者,侵删. 引言 常关注本blog的读 ...

  7. NOI数据结构:后缀树

    NOI数据结构:后缀树 后缀树_fanzitao的专栏-CSDN博客_后缀树 后缀树 - sangmado - 博客园 字符串-后缀树和后缀数组详解 字符串-后缀树和后缀数组详解_吴泽龙的博客-CSD ...

  8. O(n)线性构造后缀树详解(一)

    声明: 此为 Esko Ukkonen 论文翻译,由于本人才疏学浅,为了使用后缀树来进行DNA匹配,翻译此论文,完全是顺带之举,如有错误,请见谅!同时也是发现网上类似资料都不完整,顾发出翻译原版论文来 ...

  9. 使用后缀自动机求后缀数组

    倒序建立后缀自动机的fail树就是后缀树,dfs后缀树得到后缀数组 #include <bits/stdc++.h> using namespace std;int last,dis[20 ...

最新文章

  1. 网易是世界最好的公司
  2. 学术论文模式图、统计图绘制
  3. python菜鸟工具-Python3 教程
  4. AI - 深度学习之美十四章-概念摘要(8~14)
  5. haproxy调度web案例
  6. 栈的应用就进匹配_笔记
  7. BUAA-OO-第三单元总结
  8. 刘志勇:微博短视频百万级高并发架构
  9. React Native之组件(Component)生命周期学习笔记
  10. 图解算法学习笔记(五):散列表
  11. mongodb查询文件服务器的数据,服务器端知识库mongodb基础篇
  12. 在Blazor中构建数据库应用程序——第3部分——UI中的CRUD编辑和查看操作
  13. 网页模板快速建站工具_自助建站相对传统建站有什么优势 - 建站极速通
  14. Linux下gcc/g++、make和cmake的区别
  15. python无头浏览器截图_selenium3使用谷歌无头浏览器、截图
  16. 微信小程序获取数据并展示
  17. 访问不了共享文件夹提示“网络错误“的解决方法
  18. python学习答案_乐学Python答案
  19. 小米手机夜间模式在哪设置?仅需2个步骤
  20. 哈工大视听觉信号处理——听觉部分报告——一种智能家居命令词识别系统的设计

热门文章

  1. Vue 入门:el: '#app' 是什么意思 component 组件 实现 ToDoList
  2. concurrentqueue介绍
  3. 读《Optimally Tuned Iterative Reconstruction Algorithms for Compressive Sensing》有感……
  4. Java—抽象类和接口
  5. Shader学习笔记(三)
  6. android 3个控件平分,Android 布局平分间距。想了好久终于找到了投机取巧的办法...
  7. android 充电接口,苹果接口将改“华为口”,很多人不懂,安卓充电口为何叫华为口?...
  8. 这些区块链公司为什么被谷歌看上了?
  9. 那些年薪百万的人都在靠什么赚钱?
  10. 知了堂|MySQL 事务介绍