总的来说,本发明涉及在数据库中使用关键字进行的信息索引和定位;具体地说,是涉及一种用于索引一个数据库的前缀搜索树。 数据库特别是那些计算机系统中的数据库中经常出现的一个问题是对存贮在数据库中的某条具体信息的搜索和定位。这种搜索通过是通过下面方法来实现的:即先为数据库建立一个目录(或叫索引),然后使用搜索关键字在索引中搜索从而找到数据库中的信息的最有可能的位置地指示字。
具有最普通的形式的数据库的索引通常表现为一颗包括一个或多个通过分支连接在一起的结点的树。每个结点通常包括一个或多个包含用于指导搜索的分支区域,每个这样的分支区域中通常包括至另一个结点的指示字(或叫分支)和一个指示出位于沿该结点出发的分支上的信息范围或者类型。树以及对树所作的任何检索从一个称为根结点的单一结点出发,沿着各种分支节点向下,直到到达包括某条信息或者(更一般的情况下是)该信息的指示字的节点为止。与点有关的信息常被称作叶子节点或者失败节点,因为检索的成功与否就在这一级决定。应该指出的是,一颗树内的任何结点对于从属于该结点的所有结点而言是根结点,一颗树内的子结构通常是指相对于上述结点的子树。
在对一颗树进行检索过程中,每遇到一个结点时都应该通过把一个或多个检索关键字和存贮在该结点中的分支关键字比较而决定沿哪个方向(或叫分支)前进。上面的比较结果将决定从属于一个给定结点的分支中的哪一个将在检索过程中的下一步中被选中。这里的检索关键字最常见地都包括一个由与被检索的一条或多条信息有关的字符或数字组成的字符串。举个例子来说,“Search”,“tree”,“trees”和“search tree”等可以是为了寻找“检索树”方面的信息而对一个数据库索引进行检索用的关键字,而“617”和“895”则可以是检索617区域的895交换局的所有电话号码的关键字。正象下面将要讲到的那样,分支关键字所采用的形式取决于检索树的种类。
在现有技术中有许多检索树结构,其中有很显然是后来的所有树结构的祖先、检索树的最通用的形式“B-树”。B-树是一种多路径树,其中每个结点都具有(AoKo)……(AiKi)……(AnKn)的形式,其中的每个Ai是至该结点的一个子树的指示字,每个Ki是与上述子树有关的关键字值。由Ai指定的子树中的所有关键字值均小于Ki+1的关键字值,子树An中的所有关键字值均大于Kn,每个子树Ai都可以是多路径检索树。在一个结点决定走哪一条分支时,先把检索关键字Kx与该结点的分支关键字Ki相比较,然后沿着与大于Kx的最小值关键字Ki相对应的指示字Ai前进;如果Kx小于所有的关键字Ki,则检索将沿指针Ao前进;如果Kx大于关键字Kn,则检索将沿指针An前进。
从基本的B-树得到的下一个变型是二进制树。这种树中的每一个结点都具有统一的形式(Ai、Ki、Ai+1)。因此,二进制树中的每个结点都只包含一个分支关键字和二个分支,这从任何结点出发都只有二条(二进制)分支。当检索关键字小于结点关键字Ki时,选择最左端的分支Ai,当检索关键字Kx大于Ki时,则选择最右端的分支Ai+1。
B′-树和B*-树与B-树基本相似,只有下列不同:在B′-树中,所有的信息或者信息指示字都只能在树叶结点(即树中的最低级结点上)被找到;而在B*-树中,所有的失败结点(亦即树叶结点)都在树中的同一级别上。B*-树对从属于根结点和分支结点的分支的最小和最大数量也有具体限制。
比特树在其根结点和分支结点方面与B-树相似,但在树叶结点方面与B-树不同:比特树在树叶结点上并不存贮关键字;相反,树叶结点上的每个指示字都有一个与它对应的“区别位”,该“区别位”指示着分支的关键字与包含在根结点或下一个高于该树叶结点的结点中的分支字之间的第一个不同的位。区别位是这样产生的:先把一个树叶结点中的指示字的二进制表达式与其根结点的结点关键字的二进制表达式相比较,并且发现出最低阶的二个关键字相异的二进制数。接下来,这个数(实际上是区别数或叫相异位)被存入与指示字无关的树叶结点中。检索开始时,在树叶结点这一级上,把检索关键字与该树叶的母结点的结点关键字相比较,从而确定检索关键字与结点关键字相异的最低位,接着取出与下一阶区别位有关的树叶指示字。
Trie是一种使用可变长度关键字值的索引树,其中在Trie树的任一级上的分支都只由关键字的一部分而不是整个关键字来确定。另外,Trie中任一级上的分支都只由关键字的相应的顺序字符来确定,也就是说,在trie中的jth级上的分支由关键字中的jth字符所确定。在一个Trie中检索关键字值Kn时需要把Kn分解成其各个组成字符,再遵循那些由这些组成字符所确定的分支值。假如说,Kn=LINK,则第一级分支由与L部分对应的分支确定;第二级分支由I部分确定,第三级由N确定,第四级由K确定。这就要求检索关键字中所有字符在第一级上被分离成单独的、互不相连的数类,并且每一类中都有一个第一级分支;Trie树中包含与最长的期望检索字中的字符数相对应的多个级。
最后要提一下的是前缀B-树,其中每个结点还是具有(AoKo)……(AiKi)……(AnKn)的形式。前缀B-树中的检索方式与B-树相同,只是前缀B-树中的每个关键字Ki不是完整的关键字,而是完整关键字的一份子或者只是其前缀。前缀B-树中任何子树中每个结点上的关键字Ki都有相同的前缀,该前缀存贮在该子树的根结点上。一个结点上的每个关键字Ki是从属于该结点的相应分支的子树中所有结点的公共前缀。前缀B-树也有一个二进制变形,叫做前缀二进制树。这种树中的每个结点只含一个分支关键字和二个分支,这样,从任何结点出发都只有二个(“二进制”)分支。前缀二进制树的检索方法与二进制树相同,即根据检索关键字是否小于或者大于结点关键字来决定向左还是向右分支。另外前缀二进制树也有比特树变型,但这种树中存贮在结点中的是区别位而不是前缀。具体地说,被存贮的值是关键字中的二个前缀之间不同的比特的二进制数,这就指示出了在决定向右还是向左分支时应该测试的关键字位。
上面描叙的现有的几种检索树一般都用来为最普遍的信息检索场合和最普通类型或叫类别的信息提供最佳的特性。其中有些树可能是设计成(比方说)提供最小的树深度,从而减少把连续的多个结点或结点组装入系统存贮器所需的磁盘访问次数,或者是提供最少的检索时间,或者是使所有检索的检索时间均衡化,或者是允许方便地进行结点的插入和删除。但是现有的树结构并不能为种类广泛的信息提供最佳结构。用举例来说,现有的树结构在因某些信息所需而把关键字分成较大的部分时通常不是最佳的,对于建立和修改这些类型的关键字和信息的检索树也不能提供最佳的结构。
现存的树结构中的另一个缺点是通常必须检索到数据记录这一级才能确定某一项数据项是否在数据库中。另外,“所有的失败结点必须在同一级别上”被描述成一种要求。这一缺陷是由树结构确定的固有的检索方法引起的。正如上面所述的那样,检索关键字被和结点关键字加以比较从而确定具有最可能包括检索字的匹配值的关键字值范围的分支路径。由于检索是根据识别具有关键字值范围的分支来进行的,因此,在没有实际的数据记录的检索中无法确定检索关键字能实际上与一个数据记录相匹配。
本发明提出的前缀索引树为现有技术中的上述问题和其它一些问题提供了答案。本发明特别适用于那些其关键字可以拆成几个大的部分的信息。本发明的树结构还为建立和修改这些类型的关键字和信息的检索字提供了一种经改进了的结构。本发明的树结构在确定某一数据项不在一个数据库中之前并不要求所有的检索都进行到数据记录这一级。
本发明的树结构提供了一种用于通过与存贮在数据记录中的信息有关的关键字在一个数据处理系统中查找存贮在数据库中的数据记录的前缀索引树结构。树中的每个结点均包括一个用于存贮由一个结点中的所有子树所共享的关键字字符组成的最长的字符串构成的、长度为P的前缀字符串的前缀字段和一个用于存贮其关键字由上述的前缀字符串构成的那个数据记录的参考的数据记录字段。当上述的前缀字符串是存贮在结点上的至少一个子树中的多个关键字的前缀时,树结构还可以包括一个或多个分支字段。对于子集中的多个关键字中的每个不同的第P+1个关键字字综都有一个分支字段,其中每个不同的第P+1个字符是分支字符。每个分支字段包括一个用于存贮关键字的第P+1个字符的分支字符字段和一个用于存贮一个含有至少一个其第P+1个字符是分支字符的关键字的子树中的一个结点的参考。
在本发明的另外几个实施例中,每个结点还包括一个用于存贮与前缀字符串中的关键字字符数相等的一个数的装置和一个用于存贮与各个结点中分支字段数相等的一个数的字段。
本发明中还包括了建立和检索本发明的前缀索引树以及在树中插入结点和从树中删除结点的方法。
本发明的上述的其它一些目的、特征和优点将在下面根据附图对本发明和其实施例所作的详细描述中清楚地体现出来。附图中,
图1.是一个数据处理系统和一个常驻于其中的一个索引树的示意图,
图2.是本发明的一颗树中的一个结点的示意图,
图3.是本发明的一颗树的示意图,
图4A、4B和4C。是表示在一颗树中插入结点的示意图。
图5.是表示从树中删除结点的示意图。
A.数据处理系统中的树(概述,见图1)
参照图1,图中示出了一个数据处理系统10和一个索引树12,树12被表示成常驻于系统10的可寻址存贮器空间中。系统10包括一个中央处理单元(CPU)14和一个直接寻址存贮器20。CPU14包括一个与工作寄存器18相连的算术逻辑单元(ALU)16,直接寻址存贮器20还可以包括一个高速缓冲存贮器和一个磁盘22形式的相关存贮器。
树12被表示成具有单一的根结点24的许多全部通过指示字或叫分支30相连的分支结点(简称结点)26和树叶结点(简称树叶)28。如图所示,分支结点26根据它们在树12中的级别(亦即深度、也就是要到达一个给定结点必须经过的结点数)还能被指定。在这张树表中,有二个1级分支结点,每个被标为L1结点26;有多个2级和3级分支结点,每个分别被标为L2结点26或者L3结点26;有一个4级分支结点,被标为L4结点26。
树12在图1中相对于系统10的位置还表示出了树12的各个单元在系统10的地址空间中的位置,从系统10向右指向的箭头指出了系统10的地址空间中的各个区域的边界。举例来说,检索开始时,如图所示,根结点24(和一个或多个L1结点26以及一个或多个L2结点26)最可能位于系统10的CPU14的工作寄存器18中,故被ALU16直接访问。其余的结点26以及也许部分树叶28会在存贮器20中被发现,而树12的更深的结点也许会被发现作为文件存贮在磁盘22中。
树12的各个结点在系统10的地址空间中的位置还影响到结点以及存贮在结点中的指示字30的具体形式。举例来说,正象下面对本发明的树12的详细描述中所指出的那样,每个结点总是具有相同的基本形式,即一系列包括特殊格式的特殊类型的信息的字段。驻于工作寄存器18中节点定位在具体的寄存器中,而定位在存贮器20中的结点则驻在可以动态重赋值的,可以通过逻辑地址定位的物理存贮器单元中。驻于磁盘22中的结点将驻在磁盘文件上。驻于工作寄存器18中的结点的指示字30可以采取逻辑地址指示字更有可能采取具体的ALU16的寄存器识别码的形式。定位于存贮器20中的结点的指针字30可以采取逻辑地址指示字的形式,这些逻辑地址指示字在与它们相应的结点被访间时被系统10翻译成存贮器20的物理地址。驻于磁盘22上的结点的指示字30将采取文件参考码的形式。应该指出的是,虽然一个结点的各字段中所包含的信息的具体形式可能会因结点在系统10中的地址空间的位置而变化,树12的各结点的功能、结构和逻辑关系将保持不变。
结点在系统10的地址空间中的位置还将影响系统10访问结点和处理结点中所含的信息的速度,因此也影响系统10执行检索的速度。举例来说,驻于工作寄存器18中的结点可以直接被ALU16访问,因此,可以在较少的时间内被处理。驻于存贮器20和任何相应的高速缓存中的结点也能相当快地被CPU14访问,检索时只需要等待从逻辑到物理地址的翻译和读入工作寄存器18造成的一个存贮器访问周期的延时。但是,沿树12检索得越深,对树12中的结点的访问时间也就越大。具体说来,驻在磁盘22上的结点需要一次磁盘访问操作,把读出的文件送入存贮器20中,再送入工作寄存器18中。因此,树12越“平”越好,亦即尽可能地多包含一些分支,把结点向根结点方向移动从而减少结点存取时间,具体地说是减少对树12进行检索时所要求的磁盘访问次数。把树叶结点28尽可能地向上移入树12的结构中而不是要求所有的树叶结点28处于相同的(最低)级别上,也是有好处的。正象下面要指出的那样,本发明的树12为一些种类广泛的信息提供了一种途径,并且具有上述优点。
B.有关本发明的树的描述(图2,图3)
本发明的树12被设计成用于关键字因为和其他关键字其享几个起始字符而被分成几个大部分时的场合。树12具有使用可变长度、字符指向型关键字的密集索引结构。任何级别上的分支由关键字的一部分而不是由整个关键字所决定。树12的结构与构成顺序无关。
本发明中的树12是一种要么为空、要么具有大于或等于1的高度亦即包括一个或多个级别的前缀搜索树,它满足下列特性:
(ⅰ)树中的任何结点都具有下列形式和类型:
P,S,(Pi……Pp),D,((Bi,Si)……(Bs,Ss))
其中Pi(o<i<=s)表示前缀字符串,元(SiSi)(o<i<=s)分别是分支字符串和子树T,D是至一个数据记录的指示字;
(ⅱ)前缀(Pi……Pp)中包含T(以及从属于T的子树)中所含的每个关键字所共享的起始字符组成的最长字符串;
(ⅲ)D是和长度为P的关键字指向数据记录的指示字,无关键字时为空;
(ⅳ)每个Bi(o<i<=s)都是一个区别字符,它们是T中的一些关键字(亦即从属于T的子树中长度大于P的关键字)的P+1st字符;
(ⅴ)Bi<Bi+1,o<i<s;
(ⅵ)每个Si都是以属于T的前缀检索树的指示字;
(ⅶ)树中由Si(o<i<s)表示的关键字是由T中以Bi作为P+1st字符的那些关键字去掉原有的P+1个字符而形成的。
参照图2,图中是根据上面给出的定义而构成的本发明的树12的单结点(T)32的结构和格式的示意图。如图所示,T32可以包含一个前缀字段(PF)34和一个数据指示字段(D)36。PF34包含一个长度为P(P1……Pp)、由从属于结点T32的每个子树的所有关键字所共享的字符组成的最长字符串组成的前缀;数据指示字段(D)36包含一个至具有关键字(P1……Pp)的数据记录的指示字30,如果存在这样的关键字和数据记录的话。T32可以包含一个或多个分支字段(BF)38,每个分支字段38由一个用于存贮分支字符Bj和一个用于存贮相应的分支指示字Sj和分支指示字段(BP)42组成。正如上面指出的那样,每个Bj一个从属于T32的子树中的长度大于P的关键字中的第P+1个字符,而每个相对应的Sj是指示该子树的结点T32的指示字。最后,每个结点T32包括一个P字段44和一个S字段46,它们中分别包含存贮在PF34中的前缀的长度(或字符数)和从属于T32的子树(或数据记录)的数量(亦即结点T32中包含的BF32的个数。虽然P字段44和S字段46不是结点T32的结构中的必需部分,它们在系统10对结点进行处理时能提供帮助。具体地说,把PF36中包含的前缀的长度和分支字段的数量通知处理器要比让系统从PF36和BF38中取出这些信息有效得多。
正如下面参考图3将要描述的那样,本发明的树12中的某些结点可以是结构与分支结点T32完全相同只是不包含分支字段38(因分支为空白)的“树叶”结点。
参照图3,图中是使用“Btree”,“Binary”,“BinarySearch”,“Binarytree”,“Hashtable”,“Hashfunction”和“HashedFile”等关键字值的本发明的树12。
检查本实施例中所用的关键字可以很明显地看出,图3中的树12具有二个从属于根结点的分支(或叫子树)。一个分支包括那些关键字的首字符为“B”(Btree,Binary,BinarySearch及Binary Tree)的结点,另一个分支包括那些关键字首字符为“H”(HashTable,HashFunction及HashedFile)的结点。因此,根结点T32A的PF34将为空值,因为以“B”打头的关键字和与“H”打头的关键字之间设有共享公共前缀;T32的D字段36也将是空值,因为没有数据记录从属于T32A。T32A中包含一个用于包括所有以“B”打头的关键字的T32A子树的第一BF38字段和一个用于那些以字符“H”打头的关键字的第二BF38。现在来看第一个BF38字段,该字段中的BF40字段Bj字符将是字符“B”,因为“B”是对应于子树T32A中的关键字中的第P+1个字符。BP42字段中将包含一个指向子树T32B中的第一个结点的Sj指示字SB。T32A中的第二个BF38字段在BC40字段中包含着字符“H”作为它的Bj,因为这是相应的子树中的关键字的第P+1个字符,BP42字段中的Sj指示字将是指向该子树T32C的第一个结点的指示字SH。T32A中的P字段44和S字段46分别包含一个指示着T32A中的PF34字段中不包含前缀字符(即为空)的“0”和一个指示着T32A有二个“儿子”(即从P32A有二条分支)的“2”。
现在来考察T32B,以字符“B”打头的关键字接下来将在以“t”为第二个字符的关键字“Btree”和以“i”作为其第二个字符向关键字(Binary,BinarySearch,和BinaryTree)之间发生分支。从这一结点发生分支的关键字之间没有相同的前缀字符,因此T32B的PF34字段中包含一个空值,D字段36也是如此。T32B也有二个BF38,其中第一个具有的Bj为“i”,第二个具有的Bj为“t”;“i”和“t”是从属于这些分支的子树中的关键字的第P+1个字符。相应的Sj指示字分别是指向结点T32D和T32E的指示字Si和St。T32B的P字段44和S字段46将分别包括一个指示着PF34字段不含前缀字符的“0”和一个指示着T32B有二个孩子即分支的“2”。
现在来看T32E,这个结点包含着一个数据记录的参考,但是没有通向其他结点的分支。因为这样,T32E中的PF38字段为空(亦即该结点不包含PF38字段。T32E中的PF34字段中包含着相对应的数据记录的关键字的最后部分(在T32E的情况下为字符串“ree”)和一个包含指向该数据记录的指针的D字段36。P字段44和S字段45分别为指示着PF34字符含有3个字符的“3”和指示着树叶48A没有至于树的分支的“0”。
下面来看从属于结点T32B的另一个结点T32D。以P32D为根结点的子树包含着关键字“Binary”,“BinarySearch”和“BinaryTree”,这些关键字中的前缀“B”和“i”作为前缀分别存贮在T32A和T32B的PF34字段中。这些关键字的所剩部分“nary”,“narySearch”和“naryTree”之间最长的相同前缀为字符串“nary”。这样,字符串“nary”就作为字符串存贮在T32D的PF34字段中。
在该子树中的所有三个关键字在“nary”之后的字符各不相同,因此,T32D可以有三个分支。但是由于“nary”是关键字“Binary”的最后部分,因此,关键字“Binary”将使一个指向与该关键字有关的数据记录的指示字写入T32D的D字段36中,而不是产生至另一结点的一个分支。
关键字“BinarySearch”和“BinaryTree”在“nary”之后还有剩余字符串,因此将从T32D上产生分支。“BinarySearch”中的第P+1个字符为“S”,因此,“S”作为第一BF38中的Bj出现,在BP字段42中也出现一个指向相应的结点T32F的Sj指示字Ss。“BinaryTree”中的第P+1个字符为“T”,因此,“T”将作为Bj出现在第二BF38中,在BP字段42中也将出现一个指向相应的结点T32G的Sj指示字ST。T32D字段中的P字段44和S字段46中分别包含一个指示出PF34字段含有一个4个字符的字符串的“4”和一个指示着从T32D出发有二个分支的“2”。
T32F和T32G在下列方面与T32E相似:即这些结点不包含通向其他结点的进一步分支,因而具有空白的(或叫空的)BF38字段,而在各自的D36字段中却具有指向相应的数据记录的指示字。T32F中的PF34字段中包含字符串“earch”,它是关键字“BinarySearch”的末尾部分;而T32G中的PF34字段中含有字符串“ree”,它是关键字“BinaryTree”的最后部分。T32F的P字段44是一个“5”,因为“earch”中有5个字符;T32G中的P字段44为“3”,因为“ree”中有3个字符,各结点的S字段46均为零,指示着从这些结点再没有分支。
现在概略地看一下由结点T32C、T32H、T32I和T32J组成的树12的右侧子树。该子树由与上面则描述的原则相同的原则构成。包含在该子树中的关键字为“HashTable”,“HashFunction”和“HashedFile”。三个关键字都有的字符“H”由于是出现在T32A的PF34中的前缀中的第P+1个字符因而包含在T32A的相应的PF38中的Bj中。正如前面所描述过的那样,T32A中的PF34中包含一个空字符串,原因是在从属于T32A的二个分支之间没有相同的前缀字符串。
对这些关键字的余下部分“ashTable”,“ashFunction”和“ashedFile”而言,最长的共同前缀字符串为“ash”,因此,“ash”出现在T32C的PF34字段中。由于这三个关键字具有一个共同的前缀字符串“ash”,因此,从T32C出发将有三条分支。这三个关系字的余下部分去掉“ash”之后的第P+1个字符分别是“T”,“F”和“e”。因此,“T”,“F”和“e”将作为T32C中的BF38中的Bj出现,同时出现的还有指向结点T32H,T32I和T32J的相应的Sj指示字SF、ST和Se。T32C中的P字段44和S字段46分别包括一个表示PF34中有一个3个字符的字符串的“3”和一个表示从T32C出发有三条分支的“3”。
结点T32H,T32I和T32J也是“树叶”结点,因为它们在D字段36中含有指向数据记录的指示字,但是没有进一步的分支因而也没有BF38。T32H中的PF34字段中含有字符串“unction”,它是关键字“Hash Function”的余下部分;而T32I和T32J中的PF34字段中分别含有关键字“HashTable”和“HashedFile”的余下部分“able”和“dFile”。由于从这些结点没有再出现分支,因此各结点的S字段46中为“0”,P字段44中各自为“7”,“4”和“5”,分别表示存贮在各自的PF34字段中的关键字的余下部分中的字符数。
C.树12的检索
为了在本发明的树72中检索任何给定的关键字值,系统10将象下面将要描述的那样从根结点开始,沿树12逐个结点向前检索,直到到达一个失败结点(一个与检索关键字不匹配的结点)或者成功地找到与检索关键字相对应的数据记录为止。
从根据点开始,系统把具有长度(或叫字符数)为K的检索字(K)与长度为P、存贮在结点的PF34中的前缀字符串(P)加以比较,以判别该前缀是否与检索字的至少起始字符是否匹配,也就是说,对于i<=P而言,K>=P还是Ki=Pi。这里应该指出的是:如果前缀P=0,也就是P为空字符串,则视为检索关键字和前缀之间没有字符匹配。
如果检索关键字K和前缀P之间完全匹配即P=K,则相应的数字记录由存贮在该结点的D字段36中的指示字指定。
如果长度为P的前缀字符串和检索关键字字符串中的前P个字符相匹配,系统检索BF=38的BC40字段中的各个Bj,以找到一个能与关键字K的第P+1个字符(Kp+1)相匹配的Bj。如果在检索中没有发现Bj=Kp+1,则该结点中不含有此关键字值,检索也就失败。
如果检索中找到一个Bj等于Kp+1,则检索遵循相应的Sj指示字到达下一个结点,并继续进行检索。应该记住的是:树中每个后续结点中的前缀由各关键字的余下部分间最长的共同前缀字符串去掉已经结合到前面的结点中的前缀中前导前缀字符串后形成。用来检索树中下一个结点的关键字就具有了一个类似的新关键字值:Kp+2……Kk,换句话说,由检索关键字去掉在前面结点中与前缀和分支字符相匹配的那些前导关键字字符后剩下的部分构成。
在下面的示例性检索程序清单A中,可以发现对检索本发明的树所作的更一步描述:
程序清单A-树检索
procedure PSEARCH(T,(K1……Kk))
∥对驻留在磁盘上的前缀检索树用关键字值(K1……Kk)进行检索,返回一个值(i,d);如果K不存在则i为假,不然的话i为真,d为数据记录指示字.∥
if(T=0)then return(FALSE,O)∥特例:树为空∥
X=T;n=0
loop
input node X from disk
let X define p,s,(p1……pp),D,((B1,S1)
……(BS,SS))
∥如果前缀太长,不可能与关键字匹配∥
if n+p>k then return(FALSE,O)
∥前缀与关键字中的几个前导字符相匹配∥
for i=1 to p do
n=n+1
if Kn<>Pithen return(FALSE,O)
end
∥确定该结点是否包含关键字∥
if n=k then(if D=null then return(FALSE,O)
else return(TRUE,D))
∥确定下一步去哪一结点,检索分支字符∥
n=n+1
j=1
loop
case
:j>s:return(FALSE,O)
:Kn<Bj:return(FALSE,O)
:Kn=Bj:exit
:else:j=j+1
end
forever
X=Sj
forever
end PSEARCH
程序清单B-结点插入
procedure PINSERT(T,(K1……KK),d)
∥把数据记录指示字为d的关键字值(k……k)插入前缀检索树I中,d为空或者该关键字值已存在则返回假,否则,返回真。∥
if(d=null) then return(FALSE) ∥ 特例:d为假∥
if(T=null) 特例:树为空∥
then(T=MAKENODE((K1……Kk),d,());return
(TRUE))
X=T;Y=null;y=0;n=0;j=0
loop
input node X from disk
let X be defined by(p1……pp),D,
((B1,S1)……(BS,SS))
∥前缀与关键字中的前导字符相匹配∥
l=MIN(p,k-n)
for i=1 to l do
n=n+1
if Kn<>Pithen return(PREFIX(d,
n,(K1……K1),i,x,y,Y))
end
∥新关键字是否属于现有关系字的一个子集?∥
if n=k then(
if l=p then(
if D<>null then return(FALSE)
∥ 查询情况,用d代替空指示字∥
D=d;output X to disk;return
(TRUE))
return(SUBSTRING(d,n,(K1……Kk),
1+1,X,y,Y)))
∥确定下一步去哪个结点,检索分支字符∥
n=n+1
Y=j;j=1
loop
case
:j>s:return(BRANCH(d,n,(K1……
Kk),j,X,y,Y))
:Kn<Bj:return(BRANCH(d,n,
(K1……Kk),j,X,y,Y))
:Kn=Bj:exit
:else:j=j+1
end
forever
Y=X;X=S
forever
D.树的建立及结点的插入(图4A、B和C)
树12的建立可以采用与在现有树中插入新结点相同的方法来进行,可以认为一棵新树的起始结点插在了一棵空树上。由于这个原因,下面将着重描述在现有树中插入结点,这样的描述也同样适用于新树的建立。
在下列5个情况下要求在树12中插入新结点:
(a)在前缀和一个新关键词之间在其中任一字符串结束之前发生不匹配,这种情况被叫做“前缀冲突”;
(b)新关键字比前缀长,而且该关键字与整个长度的前缀相匹配,但是要么没有分支字符要么关键字中前缀的最后一个字符之后的下一字符不在分支字符中,这种情况叫做“分支冲突”;
(c)新关键字比前缀短,前缀在该关键字的整个长度上与关键字相匹配,这种情况叫做“原始子串”;
(d)新关键字的长度与前缀相等,并与前缀匹配,但是没有与该前缀相关的数据,这种情况称为“数据冲突”;
(e)树为空树时。
先考虑第一种情况即发生前缀冲突时的情况,前缀冲突时要求产生三个结点来取代发生冲突的那个结点,其中一个替代原先存在的那个结点,其他二个从属于上述结点。在这二个新的从属结点中,一个包含关键字中促使匹配失败的那个字符之外的部分,另一个包含前缀中促使匹配失败的那个字符之外的部分。替代原有结点的第三个结点中包含原有前缀中与关键字相匹配的部分,并包含两条分支,因而也包含二个BF38。一个BF38的Bj将是前缀中使匹配失败的那个字符,相应的Sj指向包含原始前缀的剩余部分的新结点;另一个BF38的Bj将是关键予中使匹配失败的那个字符,相应的Sj指向包含关键字的剩余部分的新子结点。
图4A中示出了上述操作过程,其中要在一颗中包含前缀“Hash Function”的结点T48A上加入一个新关键字“Hash Table”。原有的前缀和新关键字的起始字符串“Hash”相互匹配,而在原始前缀的“F”和新关键字的“T”处匹配就不成立。为此建立第一个新子结点T48B,其PF34中包括着前缀失败字符之后出现的原始前缀部分即前缀失败字符“F”之后的字符串“unction”。原有结点T84A中有一个指向一个数据记录的D字段36指示字,因此,第一个新的子结点T48B中已有一个指向同一数据记录的D字段36指示字。如果结点T48A中包含一个场BF38,它也应该出现在新子结点T48B中。
第二个新子结点T48C中的PF34中包含有关键字在关键字失败字符之后出现的部分,即关键字失败字符“T”之后出现的字符串“able”。第二新子结点T48C中也包括一个指向与关键字“HashTable”有关的数据记录的D字段36指示字。
最后,替代原结点T48A的新结点T48D的PF34中具有字符串“Hash”,即前缀和关键字字符串之间匹配的部分。新结点T48D的第一BF38中包含一个Bj“F”(它是造成不匹配的前缀字符)和一个指向具有前缀“unction”(原有前缀的余下部分)的新子结点的相应的Sj指示字。新结点T48D中的第二BF38的Bj中包含造成不匹配的关键字字符“T”,并且包含一个指向具有前缀“able”(关键字的余下部分)的相应的Sj指示字。虽然原来的结点T48A中有一个指向一个数据记录的D字段36指示字,但这个指示字现已出现在第一个子结点T48B中,因此这个原结点T48A的替代物中就没有D字段36指示字。
接下来考虑分支冲突的情况,分支冲突需要建立二个结点来取代发生冲突的结点;其中一个结点是子结点,其PF34中包含关键字中原始结点的分支字符Bj中未发现的那个字符以外的部分;另一个新结点中包含发生分支冲突的那个原有结点中的前缀、分支字符和子树,还增加一个分支字符Bj,这个新的分支字符是原有结点中作为分支字符未被找到的那个关键字字符。与这个新的分支字符有关的还有一个指向新的子结点的Sj指示字。
图4B中示出了上述操作,其中要在图4A所示的操作产生的树中加入一个新关键字“HasheolFile”。新关键字“HasheolFile”比结点T48D的前缀“Hash”长,并与整个前缀相匹配。但是该关键字的下一个字符“e”却未在T48D的BF38中发现。因此,要生成一个新结点T48E,该结点的PF34中应包括关键词在未被发现的分支字符“e”之后的部分“dFile”作为前缀。为具有分支字符“e”的T48D生成一个相应的新的BF38和指向新结点T48E的相应的Sj指示字。应该注意的是:新结点T48E中包含一个指向与关键字“HashedFile”有关的数据记录的D字段36指示字,而结点T48D和T48C则保持不变。
现在一个起始子串的情况,当遇到一个起始子串时,则需建立二个结点来取代检测到冲突的那个结点。第一个结点除了包括原有结点的子树和分支字符外还在某PF34中包含前缀中与关键字不匹配的部分减去首字符。另一结点的PF34中包含与关键字匹配的前缀部分加上关键字中的失配部分的首字符作为其唯一的分支字符,该结点还包含指向前一结点(它是这个第二结点的子结点)的相应的Sj指示字。
图4C中示出了上述操作,图中要在具有前缀“BinarySearch”和指向一个数据记录的D字段36指示字的结点T48F中加入关键字“Binary”。关键字和前缀共有的字符串“Binary”互相匹配,而前缀的“Search”部分与关键字不匹配。因此,要建立一个新结点T48F,该结点的前缀是与关键字不匹配的原始前缀部分减去其首字符“S”后的字符串“earch”。T48F也有一个D字段36指针指向与原有结点有关的数据记录。如果T48F中有指向该树的其它结点的分支字符和分支指示字,这些分支字符和指示字应重现在新结点T48G中。第二个新结点T48H是由前缀“Binary”(原有前缀中与关键字相匹配的部分)和单独的分支字符“S”(原有前缀中与关键字不匹配的那部分的首字符)构成的。与分支字符“S”有关的还有一个指向新结点T48G的Sj指示字;T48H中包含着一个指向与关键字“Binary”有关的任何数据记录的D字段36指示字。
最后是数据冲突和空树的情况,如上所述,在数据冲突中,新关键字的长度等于前缀的长度,关键字与前缀相匹配但是却没有与该前缀相关的数据。数据冲突可以简单地通过向结点中加入数据和用指向上述数据记录的D字段36指示字来改变结点的方法来处理。
空树时的情况更直接。系统可以通过(比方说)选择多个提供最长的公共前缀的关键字来为树选出一个适当的树结点前缀的方法来建立一个起始结点,然后根据上面描述的方法来进一步增加结点。
在下面的示例性插入程序清单B中可以发现对上述的结点插入方法的进一步描述:
前缀冲突时的插入
procedure PREFIX(d,n,(K1……Kk),i,X,y,Y)
∥在结点的前缀部分中发生冲突,形成三个新结点U.V.W,来取代发生冲突的结点X,K和P为冲突字符Y是X的原结点Y的子树∥
which points to X
∥假定X,Y已在贮器中∥
let X define p,s,(P1……Pp),D,((B1,S1)
……(BS,SS))
let Y define Yp,Ys,(YP1……YPp),YD,
((YB1,YS1)……(YBS,YSS))
∥建立新结点U来保存关键字的余下部分和其数据∥
U+MAKENODE((Kn+1……Kk),(d),())
∥建立新结点V来保存前缀的余下部分和子树∥
V=MAKENODE((Pi+1……Pp),(D),((B1,S1)
……(BS,SS)))
∥建立新结点W来保存公共前缀和新子树∥
if Kn<Pi
then W=MAKENODE((P1……Pi-1),(),((Kn,U),
(Pi,V)))
else W=MAKENODE((P1……Pi-1),(),((Pi,V),
(Kn,U)))
∥.用指向W的指示字取代指向Y中的X的指示字,然后清除X∥
if Y=null
then T=W
else(YSY=W;output Y to disk)
KILLNODE(X);return(TRUE)
end PREFIX
分支冲突时的插入
procedure BRANCH(d,n,(K1……Kk),j,X,y,Y)
∥一个结点的分支部分发生冲突,形成二个新结点U和W来取代发生冲突的结点X,K是(B1……Bs)中未发现的字符.∥
J提供插入点.Y是指向X的,X的母结点Y中的子树∥
∥假定X,Y已在存贮器中∥
let X define p,s,(p1……pp),D,((B1,S1)
……(BS,SS))
let Y define Yp,Ys,(YP1……YPp),YD,((YB1,
YS1)……(YBS,YSS))
∥建立新结点U来保存新关键字的余下部分和其数据∥
U=MAKENODE((Kn+1……Kk),(d),())
∥建立新关键字W来保存前缀的余下部分和子树∥
W-MAKENODE((P1……Pp),(D),((B1,S1)……(Bj-1,
Sj-1),(Kn,U),(Bj,Sj)……(BS,SS)))
∥用指向W的指示字取代指向Y中的X的指示字,然后清除X∥
if Y=null
then T=W
else{YSY=W;output Y to disk}
KILLNODE(X);return(TRUE)
end BRANCH
原始子串插入
procedure SUBSTRING(d,n,(K1……Kk),i,X,y,Y)
∥一个结点的前缀部分发生下溢,建立两个新结点替代关键字耗尽的结点X.P将是下一个被检查的字符,Y是指向的X的结点Y中的子树。∥
假定X,Y已在存贮器中
let X define p,s,(p1……pp),D,
((B1,S1)……(BS,SS))
let Y define Yp,Ys,(YP1……YPp),YD,
((YB1,YS1)……(YBS,YSS))
∥建立新结点V来保存前缀的余下部分和子树
V=MAKENODE((Pi+1……Pp),(D),((B1,S1)……
(BS,SS)))
∥建立新结点W来保存公共前缀和新子树∥
W=MAKENODE((P1……Pi-1),(d),((pi,V)))
∥用指向W的指示字取代Y中的指向的指示字X,然后清除X∥
if Y=null
then T=W
else(YSY=W;output Y to disk)
KILLNODE(X);return(TRUE)
end SUBSET
end PINSERT
D.结点的删除
删除一个包含着要被删除的给定关键字的结点的第一步是找到那个结点,这要求关键字与前缀完全匹配,接着判定是否有一个与该结点相关的数据。之后,结点的删除还与从属于该结点的分支字符的个数即分支数有关。
第一种情况是结点中没有分支字符Bj,也就是说,该结点是一个“树叶”结点,由该结点形成的检索树和其子树中没有其他关键字。在这种情况下,具有与待删除的关键字完全匹配的前缀的结点将被删除;指向该结点的子树指示字和相应的分支字符也从母结点(即含有指向待删除结点的指示字的结点)中删除掉。
下一种情况是只删除结点中的一个分支字符。这就是说,与关键字匹配的前缀为待删除结点及其子树形成的检索树中的至少一个其他关键字的前导字符。待删除的结点有效地起到了一个仓库(Placeholder)的作用,因为该结点及其子树形式的检索树中保存的关键字和其他关键字的所有分支点都将出现在从属于该结点的子树中的结点中。
关键字的删除步骤如下:首先删除掉包含着匹配的前缀的结点相关的数据记录,即由该结点的D字段36指示字所指定的数据记录。下一步必须保留待删除结点的各个子结点与该树的其余部分之间的连接或分支连接。这是通过把待删除结点的前缀和分支字符与子结点的前缀加以合并,从而产生一个新的结点来替代待删除束点以及从属于该结点的各个子结点的方法来实现的。新结点事实上取代了被删除了的结点,并被原先指向被删结点的、被删结点的母结点中的分支指示字所指示。
图5中示出了删除只有一个分支的一个结点时的情形。其中左边的的附图表示原有的树,右边的附图表示已删掉一个结点的树。如图所示,该树包括一个根结节T49A,具有二个分支,因有具有二个带有各自的相应指针的分支字符“B”和“H”。“B”分支指示字SB走的是一条不进行删除操作的分支,在这里不作讨论。从属于分支字符“H”、由相应的指示字SH所指示的分支中包含关键字“Hash”,“HashTable”,“HashTableFile”和“HashTableList”。结点T49B通过结点T49A中的分支字符“A”和其PF34中的前缀“ash”而包含关键字“Hash”,它有单独的一个通过相应的分支指示字ST从属于分支字符“T”的分支和一个根据D字段36指示字的数据记录参考。在本例中,结果49B和关键字“Hash”拟从树中删掉。
结点49B的分支指示字ST指向结点T49C,结点T49C包含前缀“able”和二个分别带有指向结点T49D和T49E的分支指示字SF和SL的分支字符“L”和“F”。结点T49D和T49E中分别包含前缀“ist”和“ile”以及指向数据记录的D字段36指示字。
为了删除结点T49B,第一步先找到并且删除由T49B的D字段36所指定的数据记录。之后,必须把T49B和T49C加以合并,从而保存T49B的“孩子”一结点T49C,T49D和T49E的关键字和数据记录参考,从而维持母结点T48B(亦即T49A)和T49C、T49D和T49E之间的连结。如图5的右侧部分所示,一个包含前缀“ashTable”的新结点T49F被建立了起来,该新结点中的前缀是结点T49B的前缀“ash”和T49C的前缀“Table”的合并。结点T49F具有来自结点T49C的二个分支字符“L”和“F”以及分别指向结点T49D和T49E的相应的分支指示字SL和SF。T49A中的指向原来的、已被删掉的结点T49B的分支指示字SH现在指向新结点T49D。这样,从结点T49A至T49D和T49E的连接被保存了下来。
在结点删除的最后一种情况下,被删除的结点具有多于一个的指向子结点的分支字符,也就是说,被删结点的前缀是由该结点及其子树形成的检索树中的至少二个其他关键字的前导字符。在这种情况下,通过删除指向与被删除的关键字有关的数据记录的D字段36指示字的方法只从结点中删掉数据。该结点的前缀和分支字符必须保存,因为该结点形成了该结点的子树的二个或二个以上的关键字之间的分支点。
在下面的示例性删除程序清单C中,可以发现对上面的结点删除操作的进一步描述:
program Listing C:
程序清单C-结点删除
procedure PDELETE(T,(K1……Kk))
∥从前缀检索树T中去除关键字值(K……K)
返回一个值(i,d),如果K不存在,i则为假。
否则i为真,d为数据记录指示字∥
if T=null then return(FALSE,null)
X=T;Y=null;Y=0;Z=null;z=0;j=0;n=0
loop
input node X from disk
let X be defined by p,s,(P1……Pp),D,
((B1,S1)……(BS,SS))
∥前缀与关键字的前导字符相匹配∥
if k-n<p then return(FALSE,null)
for i=l to p do
n=n+1
if Kn<>Pithen return(FALSE,null)
end
∥关键字与前缀匹配吗?∥
if n=k then (
if D=null then return(FALSE,null)
d=D
case
:s=0:call LEAF(X,y,Y,z,Z)
:s=1:call JOIN(X,y,Y)
:else:D=null
end
return(TRUE,d))
∥确定下一部去哪个结点,检索分支子符
n=n+1
z=y;y=j;j=1
loop
case
:j>s:return(FALSE,null)
:Kn<Bj:return(FALSE,null)
:Kn=Bj:exit
:else:j=j+1
end
forever
Z=Y;Y=X;X=Sj
forever
树叶
procedure LEAF(X,y,Y,z,Z)
∥关键字在一个树叶结点上结束,我们要删掉这个结点X,以及其母结点中引导我们的分支字符和子树指示字值,(B,S).∥
∥assume X,Y,and Z are already in memory
let Y define p,s,(p1……pp),D,((B1,S1)……
(BS,SS))
let Z define zp,zs,(ZP1……ZPzp),ZD,((ZB1,
ZS1)……(ZBZs,ZSZs))
∥清除结点X.∥
KILLNODE(X);
∥建立一个新结点W来保存Y中,删掉一个子树后的内容∥
if Y=null then (T=null;return)
W=MAKENODE((P1……Pp),(D),((B1,S1)……(BY-1,
SY-1),(BY+1,SY+1)……(BS,SS)))
∥清除Y∥
KILLNODE(Y)
∥replace pointer to Y in z with pointer to W∥
if Z=null then(T=W;return)
ZSZ=W;output Z to disk
return
end LEAF
合并
procedure JOIN(X,y,Y)
∥关键字在子树的一个结点上结束,建立一个新结点来替代这个结点X和子树(B,B)的根结点∥
∥假定X,Y和Z已在存出贮器中∥
let V define Vp,Vs,(VP1……VPvp),VD,
((VB1,VS1)……(VBVs,VSVs))
let X define p,s,(p1……pp),D,((B1,S1)
……(BS,SS))
let Y define Yp,YS,(YP1……YPYp),YD,
((YB1,YS1)……(YBYs,YSYs))
∥从子树中把下一个结点读入存贮器∥
V=S1;input node V from disk
∥建立一个新结点W来保存X加上V减去一个子树后的内容∥
W=MAKENODE((P1……Pp,B1,VP1……VPVp),(VD),
((VB1,VS1)……(VBVs,VSVs)))
∥清除结点V,X∥
KILLNODE(X);KILLNODE(V)
∥用指向W的指示字取代Y中指向X的指示字。∥
if Y=null then(T=W;return)
YSY=W;output Y to disk
return
end JOIN
end PDELETE
虽然本发明是根据其方法和装置的一个最佳实施例来进行具体描述的,应该看到的是,本技术领域内的熟练人员不离开后附的权利要求中定义的本发明的精神和范围的前提下对本发明可作各种形式上、细节上和实施上的改动。