数据库应用-后缀树及后缀数组(Suffix-Bäume&Suffix-Arraz)-2
2016-02-25 15:39
453 查看
McCreight-Algorithmus
在O(n)时间内构造后缀树基本思想
在ST中插入suffisuff_i时,以下内容可以起到帮助作用:1.v是ST中从根节点到叶节点i-1的一个内部节点,那么path-label(v)=ca就是suffisuff_i的前缀。(其中c是单个字符,a是一个字符串并且a可能为空)
2.因为v是一个内部节点,故存在其他后缀suffjsuff_j(j < i-1)拥有相同的前缀。//不一定比i-1小吧???也不一定存在把???
//因为节点存在所以一点存在j,节点意味着分叉
3.因为suffj+1suff_{j+1}已经插入,那么同样的也就存在以a为前缀的路径。当这个前缀a在内部节点u处终结,那么我们就有SL(v) = u。现在就可以从节点v直接跳到节点u了。
4.如此就可以避免比较suffisuff_i的前|a|个字符
定理:(很重要)
如果v是在ST中插入后缀suffisuff_i时建立的内部节点,并且有path-label(v) = ca,c是单个字符,a是一个字符串(可能为空)。那么或者存在一个节点u,相应的path-label(u) = a, 又或者他将会在插入后缀suffisuff_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 Suffix while(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 Kante leafParent = 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 edge p = 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 s while(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 an i++; } }
//可插图
为了方便压缩,一般在使用之前要做一些准备:
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])]
//插图一张
优化:
//木看懂 回看、、、、
相关文章推荐
- PostgreSQL亚洲活动日将于3/17--19在新加坡举行
- 数据库应用-后缀树及后缀数组(Suffix-Bäume&Suffix-Arraz)-1
- 【转载】Memcache升级版:CouchBase的安装配置与使用说明
- Memcached(三)Memcached配置参数初解
- MySQL中myisam和innodb的主键索引有什么区别?
- mongodb MySQL命令对应
- 数据库应用-半结构化数据访问-2
- 数据库应用-半结构化数据模型1
- Memcached(二)Memcached Java API基础之MemcachedClient
- 数据库应用-半结构化数据模型(Semistrukturierte Datenmodelle)2
- 数据库应用-XML数据存储(XML Speicherung)-2
- ORACLE里几种锁模式
- Memcached(一)在Windows上安装和测试memcached
- 判断两个数据库中不一样的表和存储过程
- 数据库个人经验
- Oracle执行计划 讲解(二) .
- 大型数据库分析-关联式规则(Associaton-Rules)-2
- 数据库应用-XML数据存储(Speicherung von XML Daten)-1
- mysql执行脚本无法插入中文
- mysql分表的3种方法