您的位置:首页 > 数据库

数据库应用-后缀树及后缀数组(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])]

//插图一张

优化:

//木看懂 回看、、、、
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: