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

字符串以及后缀-一些定义

字符串规定以$结尾,也就是说一个n个字符长的字符串s有,s[n]=$
|s| = n 表示字符串s的长度
s[i]表示s中第i个字符,其中i=1,2,…,|s|
s[i…j]表示字符串s的从第i为起始到第j位结束的子字符串
这样就可以定义后缀了:
suff1:=s
suffi:=s[i]s[i+1]...s[s],i=2,..,|s|1
suff|s|:=s[|s|]=$()
相应的前缀(Präfix)就可以如下表达
prej=s[1]...s[j]
如此每个字符串的子字符串就可以如下表示:
s[i...j]=preji+1(suffi)
//小问题,为什么要弄个后缀呢,他有什么好处???
动机(Motivation):
后缀(Suffix)在许多重要的应用中起到了重要的作用。比如:
在Text中寻找目标字符串,定位,实现正则表达式
要求(Erfordern):
更高效的存储后缀:包括更少得空间需求以及更短的访问时间
本节主要内容:
1.后缀的数据存储结构
2.如何建立这种数据结构
3.一些后缀的应用

后缀树(Suffix-Baum)

定义

ST(s)代表字符串s的后缀树
ST(s)有|s|个叶子节点(Blätter),分别表示s的|s|个后缀
ST(s)的每个内部节点拥有至少两个子节点
ST(s)的每一条边表示s的一个子字符串
从跟节点到叶节点i的链路表示s的一个后缀 suffi
//插图两张 若能上传

深度

path-label(v)表示ST中的一个节点v
从跟节点到节点v的链路,干好对应了通过点v表示的前缀。
string-depth(v)(String-Tiefe):
就是指从跟节点到节点v的链路上得字符的总数目:|path-label(v)
tree-depth(v)(Baum-Tiefe):
就是指从根节点到目标节点v经过的节点的数目。
因为每条边上描述的字符串的长度至少为1,所以对于ST的每一个节点有:
string-depth(v) >= tree-depth(v)

右侧最大(Rechts-Maximalität)

两个后缀的最长相同前缀(Die Längsten gemeinsamen Präfixes zweier Suffixe),就是对应两个节点在ST中的最长共同路径。
字符串s中的一个重复出现的子字符串t被称为右侧最大(Rechts-Maximalität)有两个条件:
1.t至少在s中出现两次
2.t的后续字符都不全相同
比如:s=mathematik,那么t=mat而不是t=ma
ST中的每一个内部节点都对应了一个右侧最大子字符串(rechts-maximalen Teil string), 相反的如果一个字符串在ST没有找到相应的内部节点,那么他就不是右侧最大子字符串。

后缀指引(Suffix-Verweis)

已知节点v的路径描述为(即路径上描述的字符串)ca,其中c为单个字符,而a为字符串(可以为空)。节点u的路径描述是a。那么我们就称一个从节点v到节点u的指针为后缀指引,标记为SL(v) = u
性质:
每个在以节点v为根节点的子树中的后缀都有相同的前缀ca。相应的拥有相同前缀a的后缀都在以节点u为根节点的子树中。
除根节点外的每一个内部节点都有一个后缀指引(Suffix-Verweis)
节点v下得整棵子树都会在以u节点为根节点的子树下出现
SL(v)=u tree-depth(u) >= tree-depth(v)-1
//为什么是大于或等于而不是等于呢????可以举出例子但不知为何??

一般后缀树(generalisierter Suffix-Baum)

前面看到的后缀树都是单一字符串的后缀树,现在来看一下字符串集合S={ s1,...,sk }的情况。
一般后缀树(generalisierten Suffix-Baum:GST(S))包括所有字符串的后缀,也就是联合了所有的ST( Si )
每一个叶子节点都用整数Tupel(i,j)进行描述,他表示 si suffj
每个内部节点都描述字符串集S中的一个字符串的子字符串。或者他也可以用一个整数三元式表示(i,j,l)他表示 si 的[i…j]子字符串
//插图一张

词典序(Lexikalische Tiefensuche im Suffix-Baum)

所有到目前为止所说得后缀树,其中的节点都是按照字典的顺序从左到右排序的

后缀数组及LCP数组

用SA(s)表示字符串s的后缀数组
SA(s)刚好有|S|个项,每个项代表一个后缀。
SA(s)中的项是按照字典序进行排序的
经常和后缀数组搭配使用的数组是LCP(Longest Common Prefix),他表示后缀数组中相邻两个项(后缀)之间的最长相同前缀。
//插一图

由后缀树生成后缀数组

( TiefensuchedurchBaumEinträgeSuffixArray )

//Algorithmus SuffixArrayAusSuffixBaum(ST,s):
int[] suffixArray = new int[|s|];
int[] lcpArray = new int[|s|-1];
int p = 1;//aktuelle position in den Arrays数组当前位置
int minDepth = 0; //minimale String-Tiefe seit letztem //Blatt
//按照深度搜索遍历树,即兄弟间不能移动,只能父子间移动
while(<lexikograhpische Tiefensuche über ST>){
//如果当前节点是叶子节点
    if(<aktueller Knoten ist Blatt>){
        suffixArray[p] = <Beschriftung des aktuellen Knotens>;//把当前节点索引i,写入后缀数组
        if(p != 1) lcpArray[p-1] = minDepth;
        //minDepth设为当前节点的string-depth
        minDepth = string-depth(<aktueller Knoten>);
        p = (p+1);
    }else{
        //minDepth为当前节点的string-depth和minDepth的最小值
        minDepth = min(minDepth,string-depth(<aktueller Knoten>));
    }
    //移动到下一个节点
    <gehe zum nächsten Knoten in Tiefensuch-Reihenfolge>
}

//当向上移动时(父节点)缩小minDepth,向下则保持不变。
//当到达叶子节点是把minDepth设为对应节点的string-depth。
//可插图

由后缀数组和LCP数组生成后缀树

//Algorithmus SuffixBaumAussuffixArray(SA,LCP,s):

Knoten root = new Knoten();
Knoten leaf = new Knoten(SA[1]);//空字符串节点
//从根到第一个节点的边
Kante edge = new Kante(root, leaf, "$");

int p = 1; //aktuelle Position in den Arrays

while(p < |s|){
//沿着边以(|s|-SA[p]+1-LCPA[p]的距离往回移动)
<steige auf entlang edge um (|s|-SA[p]+1-LCPA[p])Zeichen>
//如果移动后刚好落在节点上
if(<Aufstieg endet an Knoten>){
    leafParent = <Knoten, an dem Aufstieg endet>;
}else{ //aufstieg endet mitten in Kante
    leafParent = new Knoten();
    //通过插入新的节点开辟新的边
    <spalte edge durch einfügen von leafParent
}
leaf = new Knoten(SA[p]);//Knoten mit SA[p] als //Beschriftung
edge = new Kante(leafParent, leaf, s[SA[p+1]+LCPA[p]...|s|];
p = p + 1;
}

//可插图,不过图上都是德语

构建后缀树

McCreight-Algorithmus:

//Algorithmus SuffixBaumAusString(s):

Knoten root = new Knoten();
Knoten leaf = new Knoten();//Knoten für ganzen String                                                     //bzw. Suffs
//从根到第一个节点的边
Kante edge = new Kante(root, leaf, suff1);//Kante von //Wurzel zu erstem Blatt

//当前后缀索引
int i=2; //aktuelles Suffix

while(i <= |s|){
//从根节点出发对比新后缀与已经生成的树,知道出现第一个不同的字符,即停止
<Steige von der Wurzel 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设为上面停止时的节点
    leafParent = <Knoten, an dem Abstieg endet>;
}else{
    leafParent = new Knoten();
    //通过插入新节点开辟新边
    <spalte zuletzt durchschrittene Kante Einfügen von leafParent>
}
leaf = new Knoten();
edge = new Kante(leafParent, leaf, s[i+m...|s|]
p = p + 1;
}

这种方法构建后缀树的所用的时间是 O(n2) //??

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值