基于双数组Trie(Double-Array Trie)的词典查询算法
如果无法正常显示,请先停止浏览器的去广告插件。
1. 基于双数组 Trie ( Double-Array Tri
e )的词典查询算法的词典查询算法
王小飞
2004-9-17
2. 提纲
词典查询算法简介
双数组 Trie 的数据结构
基于双数组 Trie 的词典查询算法
存在的问题及一些改进
3. 词典查询算法简介
在汉语信息处理系统中,汉语词典查询是
一个重要的基础环节,在整个处理过程中都需要频繁地
访问词典以获得汉语词语知识。因此汉语词典的快速查
询对整个系统的效率有着非常重要的影响。
大部分的词典结构都是基于 hash 方法。这种
方法的关键技术在于 hash 函数的设计,采用合理的方
式来调节数据块的分配,控制分布的均匀性,减少冲突
,提高空间利用率。
4. 词典查询算法简介
目前的几种典型词典查询方法:
1. 整词二分法
2.Trie 索引树法
3. 逐字二分法
5. 词典查询算法简介
整词二分法
结构:首字散列表、词索引表、词典正文
优点:数据结构简单、占用空间小。
缺点:全词匹配,效率相对来说不高。
Tire 索引树法
结构:首字散列表、 Trie 索引树结点
优点:分词中,不需预知待查询词的长度,沿树链逐字匹配。
缺点:构造和维护比较复杂,单词树枝多,浪费了一定的空间。
逐字二分法
结构:同整词二分法
优点:查询采用逐字匹配,提高了一定的匹配效率。
缺点:由于词典结构未改变,效率的提高有限。
6. 双数组 Trie 的数据结构
Trie 树:
Trie 树是搜索树的一种。
ab cd
l
…
g
t
…
e
u
…
o
…
…
m
t
…
blue
but
gem
get
利用关键码的字符,自左向右,每次插入一个得到的 Trie 树
7. 双数组 Trie 的数据结构
本质是一个确定的有限状态自动机( DFA )的词典查询算法,每个节点
代表自动机的一个状态,根据变量的不同,进行状态转
移,当到达结束状态或者无法转移的时候,完成查询。
Trie 树的空间复杂度为 O(n)
缺点:数据结构复杂,查询效率较低
为了让 Trie 实用的实现算法在空间占用较少的同时保证
查询的效率, Aoe,J 提出了用 2 个线性数组来进行 Trie
树的表示,即双数组 Trie(Double-Array Trie)
8. 双数组 Trie 的数据结构
两个数组: base[] 、 check[]
base :数组中的每一个元素相当于 trie 树的一个节点。
check :相当于当前状态的前一状态。
对于从状态 s 到状态 t 的一个转移,必须满足:
check[base[s]+c]=s
base[s]+c=t
其中 c 是输入变量。
9. 双数组 Trie 的数据结构
check
base
s
c
t
s
c
t
s
10. 基于双数组 Trie 的词典查询算法
基本思想:
对 6763 个常用汉字根据其内码相应的赋予从 1 - 6
763 的序列码,放入 base[1]-base[6763] 。若首字序列
码是 i 的词语有 n 个,设所有第二个字的序列码依次为 a
1,a2,a3,an, 则这 n 个第二字在 base 数组中的位置依次
为 base[i]+a1,base[i]+a2,…base[i]+an 。依次类推第三
字、第四字……第 k 字的位置。
如果 base[i] 和 check[i] 同为 0 ,表示该位置为空;
如果 base[i] 为负值,表示该状态为一个词语。
11. 基于双数组 Trie 的词典查询算法
数组构造
对于每一个汉字,要确定其 base[] 值,使得
对于所有以该汉字开头的词语都能在数组中放下。
即要找到一个 k=base[i] ,使得 base[k+a1],che
ck[k+a1],base[k+a2],check[k+a2],…base[k+an]
,check[k+an] 均为 0 , a1,a2…an 是以 i 开头的
12. 基于双数组 Trie 的词典查询算法
查询
t := base[s] + c;
if check[t] = s then
next state := t
else fail
endif
13. 基于双数组 Trie 的词典查询算法
双数组构造完成之后,查询实质上就是将待查词
的各字分别转换为相应的序列码,然后作几次加
法,即可查到相应的词语。查询效率是极高的。
14. 基于双数组 Trie 的词典查询算法
添加节点
当词典添加新词的时候, Trie 树中就要添加新的节
点。如果新插入的变量是 c ,则必须保证 base[base[i]+
c]=0
i
base[i]+c
如果非空,则要重置 base[i] 以及之后的相关项。
15. 基于双数组 Trie 的词典查询算法
Procedure Relocate(s : state; b : base_index)
{Move base for state s to a new place beginning at b }
begin
for each input character c for the state s
{ i.e. for each c such that check[base[s] + c]] = s }
begin check[b + c] := s; { mark owner }
base[b + c] := base[base[s] + c]; { copy data }
{ the node base[s] + c is to be moved to b + c; Hence, for any i for which ch
eck[i] = base[s] + c, update check[i] to b + c }
for each input character d for the node base[s] + c
begin
check[base[base[s] + c] + d] := b + c
end;
check[base[s] + c] := none { free the cell }
end;
base[s] := b
end
16. 基于双数组 Trie 的词典查询算法
base
check
b
base[s]
s
c
t’
S
c
t
none
base[t]
u
d
t’
17. 基于双数组 Trie 的词典查询算法
词表:啊,阿根廷,阿胶,阿拉伯,阿拉伯人,埃及
啊
根
2
胶
3
6
拉
埃
伯
7
及
11
5
4
阿
1
廷
11
Trie 树表示
人
8
9
18. 基于双数组 Trie 的词典查询算法
编码:啊 -1 ,阿 -2 ,埃 -3 ,根 -4 ,胶 -5 ,拉 -6 ,
及 -7 ,廷 -8 ,伯 -9 ,人 -10
经过四次遍历,将所有词语放入双数组中,再遍历一
遍词表,修改 base 值
if 状态 i 为一个词
if base[i]=0 then
base[i]= -I
else base[i]=(-1)*base[i]
19. 基于双数组 Trie 的词典查询算法
得到双数组如下:
下标 1 2 3 4 5 6 7 8 9 10 11
base -1 1 1 0 1 -6 1 -8 -9 -1 - 11
check 0 0 0 0 2 2 2 3 5 7 10
状态 啊 阿 埃 阿根 阿胶 阿拉 埃及 阿根廷 阿拉伯 阿拉伯人
查询时相当于从一个状态查到另一个状态。比如查“阿根
廷”,先根据“阿”的序列码 2 ,得到 base[2]=1 ,再根据“根”的
序列码 4 ,得到“阿根”这个状态的数组下标为 base[2]+4=5,check
[5]=2 ,继续,因为 base[5]=1 ,根据“廷”的序列码 8 ,得到“阿
根廷”这个状态的数组下标 base[5]+8=9 , check[9]=5 ,同时 base
[9] 为负值,表明“阿根廷”是词表中的一个词,查询完毕。
20. 基于双数组 Trie 的词典查询算法
算法的时间和空间复杂度
根据之前的分析,该算法的查询避免了字符串比较、 co
py 运算等步骤,直接进行数值计算和数组读取,因而时间上比其他
查询算法都要快。
三种查询算法的比较
查询算法名称 花费时间 (s)
逐字二分法 6.697
双编码 4.725
双数组 Trie 1.408
21. 基于双数组 Trie 的词典查询算法
空间上,对于一个空间大小为 5,650,000 字节的主
词典,增加的数组结构大概需要 1,440,000 字节,总共
占用空间 7,090,000 字节。
22. 存在的问题
在数据结构上不可避免的存在空间浪费。
构造调整过程中,每个状态都依赖于其他状态,
所以当在词典插入或删除词语的时候,往往需要
对双数组结构进行全局调整,灵活性较差。
23. 一些改进
只把词表中出现的首字词按序列码放入数组中,
而不是把 6763 个常用字全都放入 base[] 数组的
前 6763 位中。
在初始构造确定 base[] 值的时候,优先处理词
数多的首字。为了避免调整范围太大,预先留一
定空间备以后添加新词。
24. Thanks for your
attention !