数据库应用-后缀树及后缀数组(Suffix-BäumeSuffix-Arraz)-2
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相关推荐
- 后缀树和后缀数组的一些资料收集
后缀树(Suffix tree)是一种数据结构,能快速解决很多关于字符串的问题.后缀树的概念最早由Weiner 于1973年提出,既而由McCreight 在1976年和Ukkonen在1992年和1 ...
- 字符串相关处理kmp,前缀数,后缀树,后缀数组,最长回文串,最长重复字串,最长非重复字串
1. 最长回文串 一般用后缀数组或者后缀树可以解决, 用此方法:http://blog.csdn.net/v_july_v/article/details/6897097 预处理后缀树,使得查询LCA ...
- 字符串-后缀树和后缀数组详解
文章目录 后缀树 后缀数组 概念 sa[] rk[] height[] 例题 HDU-1403最长公共子串 洛谷P2408 不同子串个数 HDU-5769Substring 后缀树 建议先了解一下字典 ...
- 012-数据结构-树形结构-哈希树[hashtree]、字典树[trietree]、后缀树
一.哈希树概述 1.1..其他树背景 二叉排序树,平衡二叉树,红黑树等二叉排序树.在大数据量时树高很深,我们不断向下找寻值时会比较很多次.二叉排序树自身是有顺序结构的,每个结点除最小结点和最大结点外都 ...
- 【转】从Trie树(字典树)谈到后缀树
本文第一部分,咱们就来了解这个Trie树,然后自然而然过渡到第二部分.后缀树,接着进入第三部分.详细阐述后缀树的构造方法-Ukkonen. 第一部分.Trie树 1.1.什么是Trie树 Trie树, ...
- 从Trie树(字典树)和后缀树
从Trie树(字典树)谈到后缀树 转载:http://blog.csdn.net/v_july_v/article/details/6897097#t22 感谢作者,侵删. 引言 常关注本blog的读 ...
- NOI数据结构:后缀树
NOI数据结构:后缀树 后缀树_fanzitao的专栏-CSDN博客_后缀树 后缀树 - sangmado - 博客园 字符串-后缀树和后缀数组详解 字符串-后缀树和后缀数组详解_吴泽龙的博客-CSD ...
- O(n)线性构造后缀树详解(一)
声明: 此为 Esko Ukkonen 论文翻译,由于本人才疏学浅,为了使用后缀树来进行DNA匹配,翻译此论文,完全是顺带之举,如有错误,请见谅!同时也是发现网上类似资料都不完整,顾发出翻译原版论文来 ...
- 使用后缀自动机求后缀数组
倒序建立后缀自动机的fail树就是后缀树,dfs后缀树得到后缀数组 #include <bits/stdc++.h> using namespace std;int last,dis[20 ...
最新文章
- 网易是世界最好的公司
- 学术论文模式图、统计图绘制
- python菜鸟工具-Python3 教程
- AI - 深度学习之美十四章-概念摘要(8~14)
- haproxy调度web案例
- 栈的应用就进匹配_笔记
- BUAA-OO-第三单元总结
- 刘志勇:微博短视频百万级高并发架构
- React Native之组件(Component)生命周期学习笔记
- 图解算法学习笔记(五):散列表
- mongodb查询文件服务器的数据,服务器端知识库mongodb基础篇
- 在Blazor中构建数据库应用程序——第3部分——UI中的CRUD编辑和查看操作
- 网页模板快速建站工具_自助建站相对传统建站有什么优势 - 建站极速通
- Linux下gcc/g++、make和cmake的区别
- python无头浏览器截图_selenium3使用谷歌无头浏览器、截图
- 微信小程序获取数据并展示
- 访问不了共享文件夹提示“网络错误“的解决方法
- python学习答案_乐学Python答案
- 小米手机夜间模式在哪设置?仅需2个步骤
- 哈工大视听觉信号处理——听觉部分报告——一种智能家居命令词识别系统的设计
热门文章
- Vue 入门:el: '#app' 是什么意思 component 组件 实现 ToDoList
- concurrentqueue介绍
- 读《Optimally Tuned Iterative Reconstruction Algorithms for Compressive Sensing》有感……
- Java—抽象类和接口
- Shader学习笔记(三)
- android 3个控件平分,Android 布局平分间距。想了好久终于找到了投机取巧的办法...
- android 充电接口,苹果接口将改“华为口”,很多人不懂,安卓充电口为何叫华为口?...
- 这些区块链公司为什么被谷歌看上了?
- 那些年薪百万的人都在靠什么赚钱?
- 知了堂|MySQL 事务介绍