双数组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
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;
对于大量数据的存储和搜索,目前通常都采用索引结构来实现。一般常用的索引结构包括线性索引表、倒排表、散列(Hash)表以及各种搜索树。
线性索引是一种静态的索引结构,不利于更新,每当做依次更新时需要改变索引表中各个索引项的位置。倒排表也是静态索引,它与线性索引表一样,搜索其中数据的时候都只能顺序搜索或者折半搜索。
散列(Hash)方法则是在表项的存储位置与它的关键码之间建立一个确定的对应函数关系Hash(),使每个关键码与结构中的一个存储位置相对应。搜索时只需要对表项的关键码进行函数计算,求得的函数值即是表项的存储位置,避免了多次关键码比较,因此搜索速度比较快。由于有可能经过散列函数的计算,把不同的关键码映射到同一个散列地址上,也就是产生冲突(一般较常用的解决冲突的办法是利用Hash桶,将地址相同的关键码放入一个桶内,然后再在桶内进行查找)。所以对于散列方法来说,散列函数的设计很关键,应该选择一个计算简单并且地址分布比较均匀的散列函数,尽量减少冲突。
搜索树包括B_树、B+树、Trie树等以及它们的各种变形。不同的树根据自身的特点应用于不同的数据环境中。索引树的数据结构相对来说比其他索引结构要复杂,但是好的搜索树算法用在合适的环境下可以具有很高的查询效率。
本文首先介绍了前人提出的双数组Trie树(Double-Array Trie)算法,然后介绍我们在该算法基础上进行的优化改进。同时,在应用研究方面,我们利用优化的双数组Trie树(Double-Array Trie)算法实现了一个词典管理程序。最后通过与当前其他两种最常用的词典机制进行实验比较,证明双数组Trie树(Double-Array Trie)优化算法不仅具备非常高的查找效率,而且空间利用率也比较理想。
Trie树是搜索树的一种,它在本质上是一个确定的有限状态自动机,每个结点代表一个状态,根据输入变量的不同,进行状态转移。如图1:
图1. Trie树结构
用Trie树搜索一个关键码的时间与关键码自身及其长度有关,最快是O(1),即在第一层即可判断是否搜索到,最坏的情况是O(n),n为Trie树的层数。由于很多时候Trie树的大多数结点分支很少,因此Trie树结构空间浪费比较多。
为了减少Trie树结构的空间浪费,同时保证Trie树查询的效率,有研究者提出了用三个线性数组表示Trie树的方法,并在此基础上进一步改进,用两个数组来表示Trie树,也就是双数组Trie树(Double-Array Trie)[2]。
双数组Trie(Double-Array Trie)的数据结构是两个整数数组,一个是
图2. 双数组Trie树结构图
1.
2.
其中c是输入变量。
令
对于状态
也就是说,
数组构造完成之后,要查找一个关键码
双数组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
next state :=
else fail
endif
3.若
该算法可用于一些时间效率要求较高的大型数据处理。比如用于词典的构造和查询。在自然语言处理中,需要经常在词典中进行查询以获取词语信息,尤其是对于需要分词的汉语,对词典的查找经常占到整个信息处理过程的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索引,构成深度为2的Trie子树,对剩余的字符串再进入相应的Hash桶内进行二分查找[5];二是中科院计算所ICTCLAS系统[7]中采用的根据前两字内码计算Hash值,建立一级Hash索引表,然后进入Hash值相同的表中进行二分查找。
采用Hash索引机制的词典查找时间复杂度为O(
我们采用优化后的双数组Trie(Double-Array Trie)树算法实现了一个词典管理程序。下面我们举例说明如何用这种优化算法组织词典。
设一个词表为:“aa,aab,aad,,bc,be,bed,cd”,则该词表对应的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-1,b-2,c-3,d-4,e-5。因为词表中所有状态首字母“a,b,c”的序列码分别是1,2,3,所以必须把数组中这三个位置留给首字母。同时,因为叶子结点的表示是用该结点
访问结点2:该结点状态b,序列码为2,对应数组下标为2。有两个分支结点,两个转移的变量序列码为3,5。所以
访问结点3:该结点状态为a,序列码为1,对应数组下标为1。只有一个状态转移的输入变量a,序列码为1,
访问结点4:该结点状态为aa,已知其数组下标为5,由两个分支结点,两个转移的输入变量序列码分别为2,4。
访问结点5:该结点状态为c,序列码为3,对应数组下标为3。有一个状态转移的输入变量d,序列码为4,
访问结点6:该结点状态为be,已知对应数组下标为6,有一个状态转移的输入变量d,序列码为4,
对于其他没有子结点的叶子结点,其
最后可以得到:
数组构造完成后就可以进行查询了。如果要查字符串bec是否词表中的一个词,首先由状态b的序列号2得到
由上述查询过程可以知道,双数组Trie(Double-Array Trie)树优化算法的查询只需要进行简单的整数相加即可。查询开销只与查询词长度有关,算法时间复杂度为O(n),n为查询词的词长。下面我们通过实验来说明这个算法的空间和时间效率。
为了更好的证明优化的双数组Trie(Double-Array Trie)树算法性能,我们采用了另外两种词典算法在相同环境下进行比较。一种算法是ICTCLAS系统采用的两字Hash词典机制;另一种是普通的Trie树词典机制。实验硬件环境是CPU
我们一共进行了下面三项实验:
1. 分别用未加入优化策略的双数组Trie树(Double-Array Trie)算法与优化后的双数组Trie树(Double-Array Trie)算法生成词典的双数组文件,比较空间利用率。
实验结果为:未加入优化策略生成的数组长度为140438,加入优化策略后生成的数组长度为114624,数组长度减少了2万5千多。而且可知,当词典条数越多时,采用优化策略算法提高的空间利用率也会越高。
2.
从实验结果可以看出,双数组Trie(Double-Array Trie)树优化算法的查询速度明显要比两字Hash算法快。
3. 比较普通Trie树算法和双数组Trie(Double-Array Trie)树优化算法用于最大匹配自动分词的速度。在这里我们采用正向最大匹配法,语料库文本均为未切分的人民日报98年语料。
文本1:98年1月语料 大小为4,092,478字节
文本2:98年2月语料 大小为4,153,811字节
文本3:98年4月语料 大小为4,666,292字节
由上述结果可以得出Trie树算法的平均速度为6.8MB/秒,而优化后的双数组Trie树(Double-Array Trie)算法的平均速度为12MB/秒,几乎比前者高出了一倍。
从理论上和最后的实验结果都充分说明了优化的双数组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