数据库应用-后缀树及后缀数组(Suffix-Bäume&Suffix-Arraz)-2

来源:互联网 发布:天猫淘宝商城童装女 编辑:程序博客网 时间:2024/06/10 02:38

McCreight-Algorithmus

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

基本思想

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

伪代码

//Algorithmus SuffiBaumAusStringSchnell(s):Knoten root = new Knoten();Knoten leaf = new Knoten(1);//Knoten für ganzen String bzw. suff_iKante edge = new Kante (root, leaf, suff_i);--Kante von der Wurzel zu erstem Blattint 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 Kante    leafParent = new Knoten();    //插入新节点作为叶的父节点    <spalte zuletzt durchschrittene Kante durch Einfügen von leafParent>}leaf = new Knoten(i); //Knoten i als Beschriftungedge = 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-BaumsKante edge;int p = 1;//aktuelle Position in Pint e = 1;//Aktuelle Position in aktueller Kantewhile(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]的前缀。
2.如果不存在这样的i,那么P不在s中
3.如果存在,则寻找SA[s]满足下面条件的最大得位置j:P是suffSA[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 Sucheint right = |s|; //rechter Rand der binären Suche//m是指中间位置binary searchint 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 Zeichenint 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 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)
5.那么RMQL(i,j)为索引i和j之间最小的L(包括i,j)
RMQ为Range Minimum Query
那么就有lca(i,j) = E[RMQL(R[i],R[j])]
//插图一张
优化:
//木看懂 回看、、、、

0 0
原创粉丝点击