双数组Trie树算法的优化及其应用研究

双数组Trie树算法的优化及其应用研究

 

       王思力1,2 张华平1,2     王斌1

1中国科学院计算技术研究所     北京       100080

1中国科学院研究生院  北京       100039

[1]Email: {wangxiaofei,zhanghp,wangbin}@software.ict.ac.cn

 

 

 

摘要:本文对双数组Trie(Double-Array Trie)算法在构造方面提出了一种优化策略,即在用Trie树构造数组的时候,优先处理分支结点数更多的结点。这种优化策略可以使该算法在保证数据查找效率不变的同时,进一步减少数据稀疏,提高了空间利用率。同时我们基于该优化算法实现了一个词典管理程序,与利用其他索引机制的词典进行实验对比。实验结果充分说明,利用优化的双数组Trie (Double-Array Trie)算法的词典不仅在查询速度上优于用其他索引机制的词典,而且存储数据的空间占用也比较小。

 

关键词:双数组;Trie树;词典;自动分词

中图分类号:TP391.1

 

Optimization on Double-Array Trie and Its Application Research

WANG Xiao-Fei1,2 ZHANG Hua-Ping1,2 WANG Bin1

Institution of Computing Technology, The Chinese Academy of Sciences, Beijing , 100080, China

Graduate School of the Chinese Academy of Sciences, Beijing , 100080, China

Email: {wangxiaofei,zhanghp,wangbin}@software.ict.ac.cn

 

 

Abstract: This paper gives an improved strategy for the algorithm of Double-Array Trie in its construction, which processes the node with most child nodes first when constructing the array. This strategy can reduce the data sparseness without reducing the search efficiency, which is good for space use. Meanwhile, we implement a program of dictionary base on the improved Double-Array Trie and compare it with those of other index mechanism. The results adequately show that the improved Double-Array-Trie algorithm has a much higher search speed than other mechanisms and it needs a smaller space for data store.

Keywords: Double-Array; TRIE; lexicon; word segmentation;

1.  引言

对于大量数据的存储和搜索,目前通常都采用索引结构来实现。一般常用的索引结构包括线性索引表、倒排表、散列(Hash)表以及各种搜索树。

线性索引是一种静态的索引结构,不利于更新,每当做依次更新时需要改变索引表中各个索引项的位置。倒排表也是静态索引,它与线性索引表一样,搜索其中数据的时候都只能顺序搜索或者折半搜索。

散列(Hash)方法则是在表项的存储位置与它的关键码之间建立一个确定的对应函数关系Hash(),使每个关键码与结构中的一个存储位置相对应。搜索时只需要对表项的关键码进行函数计算,求得的函数值即是表项的存储位置,避免了多次关键码比较,因此搜索速度比较快。由于有可能经过散列函数的计算,把不同的关键码映射到同一个散列地址上,也就是产生冲突(一般较常用的解决冲突的办法是利用Hash桶,将地址相同的关键码放入一个桶内,然后再在桶内进行查找)。所以对于散列方法来说,散列函数的设计很关键,应该选择一个计算简单并且地址分布比较均匀的散列函数,尽量减少冲突。 Douglas C. Schmidt提出过一种完美Hash函数(Perfect Hash Function)生成算法[1],该算法生成的Hash函数能保证得到的Hash值没有冲突,也就是说每一个Hash值都是唯一的。但是这种算法目前来说还有缺陷,实验显示,关键词总数为300时生成的Hash表大小为1042,还只是关键词总数的3倍多一点;当关键词增长到685时,Hash表大小会增长到3833,是关键词总数的5倍还不止。由此可知,当数据量较大时用该算法生成的Hash表会很大,空间浪费会很严重。并且数据量较大时,生成一个完美Hash函数是很费时间的。

搜索树包括B_树、B+树、Trie树等以及它们的各种变形。不同的树根据自身的特点应用于不同的数据环境中。索引树的数据结构相对来说比其他索引结构要复杂,但是好的搜索树算法用在合适的环境下可以具有很高的查询效率。

本文首先介绍了前人提出的双数组Trie(Double-Array Trie)算法,然后介绍我们在该算法基础上进行的优化改进。同时,在应用研究方面,我们利用优化的双数组Trie(Double-Array Trie)算法实现了一个词典管理程序。最后通过与当前其他两种最常用的词典机制进行实验比较,证明双数组Trie(Double-Array Trie)优化算法不仅具备非常高的查找效率,而且空间利用率也比较理想。

2.  双数组Trie树(Double –Array Trie)算法及其优化

Trie树是搜索树的一种,它在本质上是一个确定的有限状态自动机,每个结点代表一个状态,根据输入变量的不同,进行状态转移。如图1

 

 

 

 

 

1. Trie树结构

Trie树搜索一个关键码的时间与关键码自身及其长度有关,最快是O(1),即在第一层即可判断是否搜索到,最坏的情况是O(n)nTrie树的层数。由于很多时候Trie树的大多数结点分支很少,因此Trie树结构空间浪费比较多。

为了减少Trie树结构的空间浪费,同时保证Trie树查询的效率,有研究者提出了用三个线性数组表示Trie树的方法,并在此基础上进一步改进,用两个数组来表示Trie树,也就是双数组Trie(Double-Array Trie)[2]

双数组Trie(Double-Array Trie)的数据结构是两个整数数组,一个是 ,一个是 。这个算法的本质就是将Trie树结构简化为两个线性数组,如图2所示:   

2. 双数组Trie树结构图

数组和 数组中的元素是一一对应的, 数组中的每一个元素相当于Trie树的一个节点,其值做状态转移的基值, 值相当于校验值,用于检查该状态是否存在。对于从状态s到状态t的一个转移,必须满足如下两个条件:

1.      

2.      

其中c是输入变量。

为数组下标, 均为0时表示该位置为空, 为负值时表示该状态为一个可结束状态。两个数组的构造方法如下:

对于状态 ,状态A在数组中下标为i ,令A 满足条件:

,

也就是说, 的值只要能使 的直接子结点都能放入数组即可。 的值确定以后,状态 在数组中的下标随即确定,分别为 ,   ,同时

数组构造完成之后,要查找一个关键码 ,只需判断 是否等于 ,如果是,则表示 Trie树中搜索到,否则,搜索返回失败。

双数组Trie树(Double-Array Trie)算法有效的降低了Trie树结构的空间浪费,但是利用该算法生成的数组中仍然还会存在较大的数据稀疏。为了进一步减少数组空间浪费,我们对构造数组的算法进行优化,在构造数组的时候加入一种排序策略,即每一次都先处理当前分支结点最多的结点。

我们考虑到在双数组Trie树算法中,每一个结点在数组中的位置,都是由其父亲结点也就是上一状态的 值决定。而一个结点其 值的确定取决于数组的当前空闲位置以及该结点的直接子结点。一个结点的直接子结点越多,该结点在找 值时所遇到的冲突也就越多。因此优先处理分支较多的结点,有利于减少冲突,避免数组增长过大,减少数据稀疏。

优化后的双数组Tire树构造算法如下:

1.  初始化活动结点列表,把第一层结点加入该列表。

2.  如果活动结点列表不为空,则在活动结点列表中选取直接子结点数最多的结点为当前结点。否则算法结束,数组构造完成。

3.  访问该结点,决定其在 数组中的值,确定各直接子结点在数组中的位置,令各直接子结点在 数组中的值为当前结点的数组下标。

4.  将当前结点的直接子结点加入活动结点列表。重复步骤2

如图3

 

 

 

 

3. Trie

以前的构造数组时的遍历顺序:深度优先或者广度优先。

加入优化策略之后的遍历顺序:S-A-C-B-F-D-E-G-H-I-J-K-L

构造完成后,查询算法如下:

1.读入输入变量

2

If  then

next state :=

else fail

endif

       3.若 不为负,重复步骤1。否则, 为一个可结束状态。

3.  双数组Trie树(Double –Array Trie)优化算法的应用

该算法可用于一些时间效率要求较高的大型数据处理。比如用于词典的构造和查询。在自然语言处理中,需要经常在词典中进行查询以获取词语信息,尤其是对于需要分词的汉语,对词典的查找经常占到整个信息处理过程的50%以上。因此,一个高效的索引结构对于提高自然语言处理系统的速度来说其重要性可想而知。

目前用于组织词典的索引方法主要有两种,一种是Trie索引树,一种是散列(Hash)表结构。基于Trie索引树的词典机制一般是对词的首字计算hash值,建立首字hash表,然后建立Trie索引树。检索时只需沿树链进行逐字匹配,不需预知待查词的长度,查找一个词所需的时间只与该词的长度有关,因而应用在汉语自动分词中速度较快。但是由于单词树枝比较多,所以Trie索引树的空间浪费比较严重。在文献[3]中的实验结果表明,Trie树结构的词典占用空间要比下面提到的基于Hash索引的词典占用空间大上将近一倍。

基于Hash索引的词典机制就是构造一种Hash函数来计算词语的Hash值,将Hash值相同的词语放入一个桶内,检索时先计算待查词的Hash值,然后进入相应的Hash桶内进行二分查找。常用的有首字Hash和两字Hash法。

首字Hash是利用首字生成Hash值,将Hash值相同的词放在同一个Hash桶中,然后在桶中进行二分查找。两字Hash则是在首字Hash基础上的一种改进。目前两字Hash有两种方法,一是对词语的前两个字依次顺次建立Hash索引,构成深度为2Trie子树,对剩余的字符串再进入相应的Hash桶内进行二分查找[5];二是中科院计算所ICTCLAS系统[7]中采用的根据前两字内码计算Hash值,建立一级Hash索引表,然后进入Hash值相同的表中进行二分查找。

采用Hash索引机制的词典查找时间复杂度为O( )NHash值相同的词条数最大值。

我们采用优化后的双数组TrieDouble-Array Trie)树算法实现了一个词典管理程序。下面我们举例说明如何用这种优化算法组织词典。

设一个词表为:“aaaabaad,,bcbebedcd”,则该词表对应的Trie树如下:

3

2

5

1

 

4

 

6

 

7

 

8

a

a

b

d

b

c

e

c

d

d

 

9

 

10

 

11

 

 

 

 

 

 

 

4:词表Trie树结构

数组初始化均为0,构造数组时结点访问顺序如结点序号。由于无论英文字母还是汉字在计算机中都由内码唯一表示,所以可用内码一一映射成序列码。假定各字母的序列码为a-1b-2c-3d-4e-5。因为词表中所有状态首字母“abc”的序列码分别是1,2,3,所以必须把数组中这三个位置留给首字母。同时,因为叶子结点的表示是用该结点 数组下标乘以-1作为其在 数组中的值,而可结束状态表示是该结点 数组值乘以-1。为了使二者不产生混淆,所以每个具有可结束状态的非叶子结点其 数组值不能等于其数组下标。

访问结点2:该结点状态b,序列码为2,对应数组下标为2。有两个分支结点,两个转移的变量序列码为35。所以 1即可,因为 。令   = =2,结点67的数组下标为64

访问结点3:该结点状态为a,序列码为1,对应数组下标为1。只有一个状态转移的输入变量a,序列码为1 可取4,因为 。令 ,则结点4的数组下标为5

访问结点4:该结点状态为aa,已知其数组下标为5,由两个分支结点,两个转移的输入变量序列码分别为24 可取6,因为 。又因为该结点是一个可结束状态,所以令 ,两个分支结点910的数组下标分别为

访问结点5:该结点状态为c,序列码为3,对应数组下标为3。有一个状态转移的输入变量d,序列码为4 可取3,因为 。令 ,子结点8的数组下标可知为7

访问结点6:该结点状态为be,已知对应数组下标为6,有一个状态转移的输入变量d,序列码为4 可取5,因为 。又因为该结点是一个可结束状态,所以令 ,其子结点11的数组下标为9

对于其他没有子结点的叶子结点,其 值用-1乘以相应的数组下标即可。

最后可以得到:

={413-4-6-5-7-8-9-10}

={0002123565}

数组构造完成后就可以进行查询了。如果要查字符串bec是否词表中的一个词,首先由状态b的序列号2得到 1,接下来的输入变量是e,序列号为5 2,所以be是一个状态,可以继续。因为 -5,接下来的输入变量c序列号为3 5,不是be的数组下标6,所以bec不是词表中的一个词。如果查的是bedd的序列号为4 6,同时 等于-9,所以可以判断bed是词表中的一个词。

由上述查询过程可以知道,双数组TrieDouble-Array Trie)树优化算法的查询只需要进行简单的整数相加即可。查询开销只与查询词长度有关,算法时间复杂度为O(n)n为查询词的词长。下面我们通过实验来说明这个算法的空间和时间效率。

4.实验比较及结果分析

为了更好的证明优化的双数组TrieDouble-Array Trie)树算法性能,我们采用了另外两种词典算法在相同环境下进行比较。一种算法是ICTCLAS系统采用的两字Hash词典机制;另一种是普通的Trie树词典机制。实验硬件环境是CPU 1.5G (AMD Athlon XP 1800+),内存 512M ,操作系统为windows xp,所用词典总共包括75784个词条。

我们一共进行了下面三项实验:

1.    分别用未加入优化策略的双数组Trie树(Double-Array Trie)算法与优化后的双数组Trie树(Double-Array Trie)算法生成词典的双数组文件,比较空间利用率。

实验结果为:未加入优化策略生成的数组长度为140438,加入优化策略后生成的数组长度为114624,数组长度减少了25千多。而且可知,当词典条数越多时,采用优化策略算法提高的空间利用率也会越高。

2.    比较两字Hash索引机制算法和优化的双数组Trie树(Double-Array Trie)算法查找词语的速度。为了更接近真实语言环境,我们首先分别用二者把词典中所有的词查一遍,再分别对语料库中出现的所有词进行查询。采用的语料库为已经切分好的人民日报19981月份语料,大小为8.04MB。实验结果如下:

 

 

 

 

从实验结果可以看出,双数组TrieDouble-Array Trie)树优化算法的查询速度明显要比两字Hash算法快。

3.    比较普通Trie树算法和双数组TrieDouble-Array Trie)树优化算法用于最大匹配自动分词的速度。在这里我们采用正向最大匹配法,语料库文本均为未切分的人民日报98年语料。

文本1981月语料 大小为4,092,478字节 

文本2982月语料 大小为4,153,811字节

文本3984月语料 大小为4,666,292字节

由上述结果可以得出Trie树算法的平均速度为6.8MB/秒,而优化后的双数组Trie树(Double-Array Trie)算法的平均速度为12MB/秒,几乎比前者高出了一倍。

5.总结

从理论上和最后的实验结果都充分说明了优化的双数组Trie树(Double-Array Trie)算法不仅在查询时间上相比Trie树有了进一步的提高,而且克服了Trie树空间浪费严重的缺点。

但是这种结构也有它本身的缺点,由数组的构造过程可以看出,每一个结点在base数组中值的确定,在一定程度上是由其分支结点决定的。而相应的base值又决定了其各个直接子结点在数组中的位置。因此,在增加数据或者删除数据的时候,往往需要对与之相关的其他数据都进行调整,调整算法参见文献[2]。如果数据经常变动,不仅用于调整数组的时间开销增大,而且当数据增加比较多时,由于调整带来的数据稀疏会越来越大,数组本身也会变得很大。

对于这种情况,我们采取的策略是当数组增大到一定程度,就重新构造一遍数组。由于重新构造数组也需要时间,所以这种算法不适用于数据变动非常频繁同时对时间效率要求又高的情况。但是对于自然语言处理中词典一般变更较少而实时性要求较高的这类情况,优化的双数组Trie树(Double-Array Trie)算法是非常有用的。

在目前研究的基础上,我们将进一步研究如何把优化的双数组Trie树算法适用于其他领域。在不同的应用领域里根据需求争取有不同的改进。

 

 

参考文献

[1] Douglas C. Schmidt.  GPERF:A Perfect Hash Function Generator[Z] 1999

[2] Theppitak Karoonboonyanan. An Implementation of Double-Array Trie[Z],

http://linux.thai.net/~thep/datrie/datrie.html  2003

[3] Jun-Ichi Aoe, Katsushi Morimoto, Takashi Sato,  An Efficient Implementation of Trie Structures[J], Software-Practice and Experience. 1992,22(9):695-721.

[4] Aoe, J. An Efficient Digital Search Algorithm by Using a Double-Array Structure. IEEE Transactions on Software Engineering. 1989,15(9):1066-1077.

[5]李庆虎,陈玉健,孙家广。一种中文分词词典新机制——双字哈希机制[J],中文信息学报 2002  17(4):13-18

[6]殷人昆,陶永雷,谢若阳,盛绚华。数据结构(用面向对象方法与c++描述)[M],北京,清华大学出版社 1999

[7]刘群,张华平,俞鸿魁,程学旗。基于层次隐马模型的汉语语法分析[J]。计算机研究与发展,2004.8

[8]路志英,林孔元,郭祺,段广玉。中文切分词典的最大匹配索引法[J]。天津大学学报,1999,32(5):599-603

[9]孙茂松,左正平,黄昌宁。汉语自动分词词典机制的实验研究[J]。中文信息学报,2000,14(1):31-36

[10]杨文峰,陈光英,李星。基于PATRICIA tree的汉语自动分词词典机制[J]。中文信息学报,2001,15(3):44-49

[11]马哲,姚敏。一种改进的基于PATRICIA树的汉语自动分词词典机制[J]。华南理工大学学报(自然科学版),2004.32(增刊):28-31

 



基金项目:973项目(2004CB318109);国家242信息安全计划资助课题成果( 2005C 36);中科院计算所知识创新工程(20056550)

作者简介:王思力,男,1981年生,硕士研究生,主要研究方向为自然语言处理,中文信息检索。张华平,男,1978年生,博士研究生,主要研究方向为中文信息处理与信息抽取。王斌,男,1972年生,硕士生导师,主要研究方向为信息检索、信息分类、信息过滤、中文信息处理等。

你可能感兴趣的:(trie)