双数组TRIE树原理

2023-11-13

 


原文名称:
An Efficient Digital Search Algorithm by Using a Double-Array Structure
作者:
JUN-ICHI AOE
译文:
使用双数组结构的一个高效的Digital Search算法

摘要:
本文介绍了一种新的内部(内部排序的内部,也就是在内存里)数组结构的digital search算法,叫做双数组,结合了数组存取的快速和链式存储的压缩。Digital search树的每一条弧在双数组中都可以以O(1)的时间复杂度计算得到;也就是说,查找一个key值最坏的时间复杂度是O(k),k是这个key值的长度。本文给出了同时具有速度和空间双重性能的双数组的查找,插入,删除算法。假设双数组的长度是n+cm,n是ds树中节点的数量,m是输入符号的数量,c是一个依赖于实现的常数;那么理论上可以证明插入和删除的最坏时间复杂度分别是cm2(插入要解决冲突,所以慢)和cm,与n没有关系。从实验的结果来看,建立双数组的时间随n增长,并且c是一个相当小的常数,从0.17到1.13。

关键词:数据库系统,数据结构,词典,digital search,动态内部存储,关键词查询算法。 ---


1.       导论
在很多信息检索算法中,很需要采用一种快速的digital search算法,或者叫做trie搜索,因为它一个字符(digital)一 个字符地查看输入。使用这种数据结构的例子有一种词法分析器,和一种编译器的本地代码优化器,一种图书搜索,拼写检查,常用单词过滤器,一种自然语言处理 中的形态学分析器等等。词典能够动态增长在自然语言处理中尤为重要,因为经常需要对词汇表添加新词(这其实是双数组的弱项- -)。本文展示的这一算法适合插入远远多于删除的情况,这样删除带来的空间浪费就可以由插入来填补。

       关键词查询策略可以被大致分为两类,按照关键词集合是否可变可以将这些算法分为“动态方法”(允许查询表被修改)和“静态方法”(显然相反)两种。广为人知的“动态方法”有:hashing,二叉树,B+树,扩展hashing,和trie hashing。而“静态方法”有:完美hashing,稀疏表,以及压缩trie。 当使用静态方法的时候我们能专注于提高查询速度和压缩数据结构,而当使用动态方法的时候我们会使用额外的空间以达到更快的更新速度。本文提出的查询方法正 好介于这两者之间,所以我称之为“弱静态方法”。将静态方法扩展到弱静态方法,同时保持前者有用的特性是十分困难的。完美hashing的扩展已经有了,但不能确定插入的时间复杂度上限。本文的目标是建立一种digital search算法,它同时具有静态方法的速度和压缩特性,以及动态方法的快速更新的能力。

       不同于基于key值的搜索方法,digital search采用一连串的字符(digit)来表示一个key。每个h层的DS树的节点表示所有以一定的h个字符开始的关键词;这个节点根据第(h+1)个字符定义它的分支。本文的基本观念是压缩trie树,使用两个一维数组base和check来表示trie树,成为双数组,并且给出更新(插入、删除)算法。Trie的每个节点使用指针指向下一个元素,每个索引元素是一个结束标志加上一个指向新节点的指针(或者null)。查询,插入,删除都非常快,但是它会占用很多空间,因为很多trie树节点是空的;也就是说,trie树是稀疏的。所以我们必须尝试映射节点r到check数组,这种映射关系由base[r]指定。

       在接下来的章节,我们会详细描述我们的想法。在第二节,我们把DS树形式化为模式匹配机器并定义双数组使用O(1)时间计算一条弧。为了将双数组应用于大的关键词集合,双数组做出了一些修改。最主要的创新是仅将足以分辨不通关键词的前缀存储到双数组,将其他部分存储在单独的string里面。插入删除算法在第三章讨论。当插入一个新的非空位置r的时候遇到另一个节点k已经占用这个位置,插入算法通过重新调整base[r]或者base[k]来解决冲突。在本文中,为减少占用的时间和空间占用组少非空未知的k或r有优先权(就是移动占用少的,占用多的不变)。第四节讨论了每个算法的最坏时间复杂度的理论值,并且通过实验验证了。双数组的部分匹配和key-order查询也做出了讨论。最后第五节做出了结论总结。

双数组trie树的基本构造及简单优化

一、 基本构造

Trie树是搜索树的一种,来自英文单词"Retrieval"的简写,可以建立有效的数据检索组织结构,是中文匹配分词算法中词典的一种常见实现。它本质上是一个确定的有限状态自动机(DFA),每个节点代表自动机的一个状态。在词典中这此状态包括"词前缀","已成词"等。
双数组Trie(Double-Array Trie)是trie树的一个简单而有效的实现,由两个整数数组构成,一个是base[],另一个是check[]。设数组下标为i ,如果base,check均为0,表示该位置为空。如果base为负值,表示该状态为词语。Check表示该状态的前一状态,t=base+a, check[t]=i 。
复制代码
下面举例(源自<<双数组Trie(Double-Array Trie)的数据结构与具体实现>>)来说明用双数组Trie(Double-Array Trie)构造分词算法词典的过程。假定词表中只有“啊,阿根廷,阿胶,阿拉伯,阿拉伯人,埃及”这几个词,用Trie树可以表示为:
我们首先对词表中所有出现的10个汉字进行编码:啊-1,阿-2,唉-3,根-4,胶-5,拉-6,及-7,廷-8,伯-9,人-10。。对于每一个汉字,需要确定一个base值,使得对于所有以该汉字开头的词,在双数组中都能放下。例如,现在要确定“阿”字的base值,假设以“阿”开头的词的第二个字序列码依次为a1,a2,a3……an,我们必须找到一个值i,使得base[i+a1],check[i+a1],base[i+a2],check[i+a2]……base[i+an],check[i+an]均为0。一旦找到了这个i,“阿”的base值就确定为i。用这种方法构建双数组Trie(Double-Array Trie),经过四次遍历,将所有的词语放入双数组中,然后还要遍历一遍词表,修改base值。因为我们用负的base值表示该位置为词语。如果状态i对应某一个词,而且Base=0,那么令Base=(-1)*i,如果Base的值不是0,那么令Base=(-1)*Base。得到双数组如下:
复制代码
下标 1 2 3 4 5 6 7 8 9 10 11 12 13 14
Base -1 4 4 0 0 0 0 4 -9 4 -11 -12 -4 -14
Check 0 0 0 0 0 0 0 2 2 2 3 8 10 13
词缀 啊 阿 埃 阿根 阿胶 阿拉 埃及 阿根廷 阿拉伯 阿拉伯人
用上述方法生成的双数组,将“啊”,“阿”,“埃”,“阿根”,“阿拉”,“阿胶”,“埃及”,“阿拉伯”,“阿拉伯人”,“阿根廷”均视为状态。每个状态均对应于数组的一个下标。例如设“阿根”的下标为i=8,那么check的内容是“阿”的下标,而base是“阿根廷”的下标的基值。“廷”的序列码为x=8,那么“阿根廷”的下标为base+x=base[8]+8=12。
复制代码
二、 基本操作与存在问题

1, 查询
trie树的查询过程其实就是一个DFA的状态转移过程,在双数组中实现起来比较简单:只需按照状态标志进行状态转移即可.例如查询“阿根廷”,先根据“阿”的序列码b=2,找到状态“阿”的下标2,再根据“根”的序列码d=4找到“阿根”的下标base+d=8,同时根据check[base+d]=b,表明“阿根”是某个词的一部分,可以继续查询。然后再找到状态“阿根廷”。它的下标为y=12,此时base[y]<0,check[y]=base+d=8,表明“阿根廷”在词表中,查询完毕。
复制代码
查询过程中我们可以看到,对于一个词语的查询时间是只与它的长度相关的,也就是说它的时间复杂度为O(1).在汉语中,词语以单字词,双字词居多,超过三字的词语少之又少.因此,用双数组构建的trie树词典查询是理论上中文机械分词中的最快实现。

2, 插入与删除
双数组的缺点在于:构造调整过程中,每个状态都依赖于其他状态,所以当在词典中插入或删除词语的时候,往往需要对双数组结构进行全局调整,灵活性能较差。

将一个词语插入原有的双数组trie树中,相当于对DFA增加一个状态。首先我们应根据查询方法找出该状态本应所处的位置,如果该位置为空,那好办,直接插入即可。如果该位置不为空。那么我们只好按照构造时一样的方法重新扫描得出该状态已存在的最大前缀状态的BASE值,并由此依次得出该状态后继结点的BASE值。在这其中还要注意CHECK值的相应变化。

例如说,如果"阿拉根"某一天也成为了一个词,我们要在trie树中插入这一状态。按计算它的位置应在8,但8是一个已成状态.所以我们得重新确定"阿拉"这一最大已成前缀状态的BASE值.重新扫描得出BASE[10]=11。这样状态15为"阿拉根",且BASE[15]为负(成词),CHECK[15]=10;状态20为"阿拉佰",且BASE[20]=-4,CHECK=10。

这样的处理其实是非常耗时间的,因为得依次对每一个可能BASE值进行扫描来进行确定最大已成前缀状态的BASE值。这个确定过程在构造时还是基本可以忍受的,毕竟你就算用上一,两天来构造也没有问题(只要你构造完后可以在效运行即可)。但在插入比较频繁时,如果每次都需要那么长的运行时间,那确实是无法忍受的。

双数组删除实现比较简单,只需要将删除词语的对应状态设为空即可――即BASE值,CHECK均为设0。但它存在存在一个空间效率的问题.例如,当我们在上面删除"埃及"这一词语时,状态11被设为空。而状态10则成了一个无用结点――它不成词,而且在插入新词时也不可重用。所以,随着删除的进行,空状态点和无用状态点不断增多,空间的利用率会不断的降低。

三、 简单优化

优化的基本思路是将双数组trie树构建为一种动态检索方法,从而解决插入和删除所存在的问题。

1, 插入优化
在插入需要确定新的BASE值时,我们是只需要遍历空状态的。非空状态的出现意味着某个BASE值尝试的打败,我们可以完全不必理会。所以,我们可以对所有的空状态构建一个序列,在确定BASE值时只需要扫描该序列即可。
对双数组中的空状态的递增结点r1,r2, …, rm,我们可以这样构建这一空序列:
CHECK[ri]=−ri+1 (1 i m−1),
CHECK[rm]=−(DA_SIZE+1)
其中r1= E_HEAD,为第一个空值状态对应的索引点。这样我们在确定BASE值时只需扫描这一序列即可。这样就省去了对非空状态的访问时间。

这种方法在空状态并不太多的情况下可以很大程度的提高插入速度。

2, 删除优化
1) 无用结点
对于删除叶结点时产生的无用结点,可以通过依次判断将它们置为空,使得可在插入新词时得以重用。例如,如果我们删除了上例中的"阿根廷",可以看到"阿根"这一状态没有子状态,因此也可将它置为空。而"阿"这一状态不能置空,因为它还有两个子状态。

2) 数组长度的压缩
在删除了一个状态后,数组末尾可能出现的连续空状态我们是可以直接删除的。另外我们还可以重新为最大非空索引点的状态重新确定BASE值,因为它有可能已经由于删除的进行而变小。这们我们可能又得以删除一些空值状态。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

双数组TRIE树原理 的相关文章

  • 以编程方式在 App Store 上运行搜索?

    是否可以从我的应用程序中打开 App Store 应用程序并运行搜索 我想看看是否有一个 appstore 类型的 URL 可以使用 就像 mailto 和 sms 分别打开邮件和短信一样 有谁知道这是否可能 编辑 更多信息 我一直在尝试使
  • 如何设计 RESTful 搜索/过滤? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我目前正在 PHP 中设计和实现 RESTful API 然而 我并没有成功地实现我最初的设计 GET users list of users
  • android 多关键词搜索

    我的应用程序包含搜索功能 它将搜索数据库内的内容 我的搜索的弱点是 我只能使用一个标签进行搜索 例如我只能搜索 猫 它会返回我的数据库中包含 猫 一词的内容 因为我正在使用LIKE在 select 语句期间进行查询 如何使用多个标签进行搜索
  • 比 BMH (Boyer–Moore–Horspool) 更快的算法

    您会使用哪种算法来搜索短文本中的短子字符串 简而言之 我的意思是子字符串有 5 10 个字符 字符串有 255 个字符 我正在考虑根据输入数据长度选择算法 哪种算法对于较长的输入更好 Try Turbo BM http www igm un
  • Android SearchView 在启动时隐藏键盘

    我有一个小问题正在尝试解决 当我打开应用程序时 键盘会显示输入搜索视图的查询 不过 我只想在单击搜索视图时显示键盘 我该如何解决 Thanks 这对我有用 用于隐藏焦点的代码 searchView SearchView view findV
  • 在 .csv 文件中搜索 C 中的名称匹配项

    我目前有一个 csv 文件 其中包含三个字段 用户 密码 类型 例如 我的文件如下所示 michael sun123 user joseph sierra7 user isaac apple2 sysop 我想从这样的文件中读取并检查用户
  • 使用 Fortran 进行数组问题的二分查找

    我正在使用 Schaum 的 Fortran 77 编程概要 一书 其中有一个关于使用括号值组方法进行二分搜索的示例 首先这是代码 INTEGER X 100 INTEGER RANGE INTEGER START FINISH PRINT
  • Twitter api - 搜索太复杂?

    知道为什么 Twitter 会抛出这个错误吗 GET https search twitter com search json q Middle 20Tennessee 20State 20Blue 20Raiders 20Florida
  • Bing 图像搜索 API 按图像大小过滤

    我正在使用 jsonp 和 jquery ajax 来使用 Bing 图像搜索 API 我能够检索搜索结果 但我无法找到按图像大小过滤结果的方法 我在文档中找不到任何与此相关的内容 有谁知道是否有一种方法可以按图像大小过滤结果或对此进行任何
  • 获取所有ios应用程序的全局列表[重复]

    这个问题在这里已经有答案了 我想对苹果应用商店进行一些全球统计 一个瓶颈是至少获取所有当前活动应用程序的 ID 这 9 位数字 有谁知道如何获取 iOS 应用商店中当前活动应用程序的所有 id 的完整列表 更好的是特定类别的所有 ID 例如
  • grep 查找 Unix 中的特殊字符

    我有一个日志文件 application log 其中可能包含以下多行普通和特殊字符字符串 Q 我想搜索包含这个特殊字符串的行号 grep Q application log 上述命令不返回任何结果 获取行号的正确语法是什么 Tell gr
  • 在 Elasticsearch php API 中使用多种类型或索引

    我想使用查询多种类型和索引Elasticsearch PHP API 但我不知道怎么办 我应该将类型和索引的数组传递给 params params index index array of indices params type types
  • Spring MVC 中的 Elasticsearch 集成?

    有谁知道如何集成spring mvc和elasticsearch吗 我想实现一个像一般网站 谷歌 雅虎搜索引擎 一样的网页 有教程或者示例代码吗 查看 Spring Data Elasticsearchproject https githu
  • 如何突出显示 html 文档中文本查询的搜索结果而忽略 html 标签?

    我有一个字符串 其中包含 html 内容 像这样的东西 const text My name is Alan and I span an span div class someClass artist div 我使用以下命令在反应组件中渲染
  • pyExcelerator 或 xlrd - 如何查找/搜索给定几列数据的行?

    Python 与 EXCEL 通信 我需要找到一种方法 以便我可以查找 搜索给定列数据的行 现在 我正在逐一扫描整个行 这将很有用 如果有一些功能 如查找 搜索 替换 我在 pyExcelerator 或 xlrd 模块中没有看到这些功能
  • Solr 阿拉伯语

    我正在使用 Solr 来索引 3 种语言 阿拉伯语 法语和英语 的文档 我使用了这个 fieldType
  • SQL 中的最佳 LIKE 搜索

    我有一个零件数据库 我将不断查询该数据库以获取报价系统 零件数据库有超过 1 400 000 条记录 用户将开始输入零件号 他们希望系统能够在仅几个字符后找到这些零件号 因此我需要能够进行通配符搜索 例如 SELECT NeededFiel
  • 如何在 Google 知识图谱中搜索具有特定属性的条目?

    应如何制定搜索查询kgsearch googleapis com查找给定类别中的所有条目 例如 如果我想搜索 Schema org 类别中的内容应用类别 http schema org applicationCategory 我该怎么办呢
  • 为什么 C# Array.BinarySearch 这么快?

    我已经实施了一个很简单用于在整数数组中查找整数的 C 中的 binarySearch 实现 二分查找 static int binarySearch int arr int i int low 0 high arr Length 1 mid
  • Laravel 搜索关系

    我有两个相关的模型 我正在尝试在产品中进行搜索 并且仅显示实际搜索结果 而不是找到该产品的类别的所有产品 我不想搜索任何类别 因为无论搜索什么或找到什么 类别都会始终显示 Example I have the following categ

随机推荐