跳转到内容

User:SmallSheepJoseph/X-fast trie

维基百科,自由的百科全书
X-fast trie
类型Trie
发明时间1982
发明者Dan Willard
大O符号表示的时间复杂度
算法 平均 最差
搜索 O(log log M) O(log log M)
插入 O(log M) amortized
删除 O(log M) amortized
大O符号表示的空间复杂度
空间 O(n log M) O(n log M)

计算机科学中, x-fast trie是一种用于存储有界域中的整数的数据结构。它支持精确查询和前置或后继查询,时间复杂度为O (n log M),使用O(n log M)空间,其中n是存储值的数量, M是域中的最大值。该结构由Dan Willard于 1982 年提出, 同时提出的还有更复杂的y-fast trie 树,目的是提高van Emde Boas 树的空间使用率,同时保留O(n log M)查询时间。

结构

[编辑]
A binary tree with 4 levels. The nodes on each level are: 3: (), 2: (0) and (1), 1: (00) and (10), 0: (001), (100) and (101). The unlabeled node is the root. There are directed edges between the following nodes: ()->(0), ()->(1), (0)->(00), (0)->(100) in blue, (1)->(10), (1)->(101) in blue, (00)->(001) twice, once in blue, (10)->(100), (10)->(101), (001)<->(100), (100)<->(101). The nodes on each level are contained in a box, labeled with LSS(<level>).
包含整数 1 (001 2 )、4 (100 2 ) 和 5 (101 2 ) 的 x-fast trie。蓝色边缘表示后代指针。

x-fast trie 是一种按位 trie :一种二叉树,其中每个子树存储的二进制表示都以公共前缀开头的值。每个内部节点都标有其子树中值的公共前缀,通常,左子节点在前缀末尾添加 0,而右子节点添加 1。 0 到M之间的整数的二进制表示 − 1 使用⌈log 2 M ⌉ 位,因此 trie 的高度为O (log M)。

x-fast trie 中的所有值都存储在叶子节点中。仅当内部节点的子树中有叶子时才会存储。如果内部节点没有左子节点,它会存储指向其右子树中最小叶子的指针,称为后代指针。同样,如果它没有右孩子,它会将指向其左子树中最大叶子的指针存储起来。每个叶子节点都存储指向其前任节点和后继节点的指针,从而形成一个双向链表。最后,每个级别都有一个哈希表,其中包含该级别上的所有节点。这些哈希表共同构成了级别搜索结构 (LSS)。为了保证最坏情况的查询时间,这些哈希表应该使用动态完美哈希或布谷鸟哈希。

总空间使用量为O(n log M),因为每个元素都有一个长度为O (log M)。[來源請求][需要引用]

操作

[编辑]

vEB树 (van Emde Boas tree)类似,x-fast 树支持有序关联数组的操作。这包括通常的关联数组操作,以及另外两个排序操作,前驅和後繼 :

  • 查找k ):查找与给定键关联的值
  • 尋找後繼k ):查找具有大于或等于给定键的最小键的键/值对
  • 尋找前驅k ):查找小于或等于给定键的最大键的键/值对
  • 插入kv ):插入给定的键/值对
  • 删除k ):删除具有给定键的键/值对

寻找

[编辑]

通过在LSS [0] 中查找k ,可以在常数时间内找到与数据结构中的键k关联的值,LSS [0] 是所有叶子上的哈希表。

例如,如果我们在上图中寻找 4,我们将执行以下步骤:

  • 步骤1:将十进制4转换为二进制,即100。
  • 第 2 步:从根开始,并尝试遵循到达每一级的路径。 100 的第一位数字是 1,因此沿着根的正确路径(1)到达节点“1”。
  • 步骤3:重复步骤2,100的第二位是0,因此沿着节点“1”的左边路径(0)走到节点“10”。
  • 步骤4:重复步骤3,100的第3位是0,因此沿着节点“10”左边的路径(0)走到节点“100”。

继任者和前任

[编辑]

要找到键k的后继或前任,我们首先找到A k ,即k的最低祖先。这是 trie 中与k具有最长公共前缀的节点。为了找到A k ,我们在层级上执行二分查找。我们从h /2 级开始,其中h是 trie 的高度。在每一级,我们使用正确长度的前缀k来查询层级搜索结构中相应的哈希表。如果不存在具有该前缀的节点,我们就知道A k一定处于更高级别,并将搜索限制在这些级别上。如果具有该前缀的节点确实存在,则A k不能位于更高级别,因此我们将搜索限制在当前级别和较低级别。

一旦我们找到k的最低祖先,我们就知道它的一个子树中有叶子(否则它不会在字典树中),并且k应该在另一个子树中。因此,后代指针指向k的后继或前任。根据我们要寻找的对象,我们可能需要在链表中向前或向后移动一步,到达下一个或上一个叶子节点。

由于 trie 的高度为O (对数 M ),二分查找最低祖先需要O (log 日志 M )时间。此后,可以在常数时间内找到后继或前任,因此总查询时间为O (log 日志 中号)。

例如,如果我们要寻找上图中 3 的前任,我们将执行以下步骤:

  • 步骤1:将十进制4转换为二进制,即011。
  • 第 2 步:从根开始,并尝试遵循到达每一级的路径。 011 的第一位数字是 0,因此沿着根的左侧路径(0)到达节点“0”。
  • 步骤3:重复步骤2,011的第二位数字是1,因此尝试按照正确的路径(1)进行操作。但是,节点“0”没有正确路径,因此请按照指针指向节点“001”。
  • 步骤4:001小于011,所以它代表011的前身。因此,3 的前身是 1(001)。

插入

[编辑]

要插入一个键值对 ( k, v ),我们首先找到k的前任和后继。然后,我们为k创建一个新叶子节点,将其插入到后继节点和前任节点之间的叶子链表中,并赋予它一个指向v的指针。接下来,我们从根节点向下走到新叶子节点,沿途创建必要的节点,将它们插入到相应的哈希表中,并在必要时更新后代指针。

由于我们必须遍历 trie 的整个高度,因此这个过程需要O (log M )时间。

删除

[编辑]

要删除键k ,我们使用叶子上的哈希表找到它的叶子。我们将它从链接列表中删除,但要记住哪个是后继,哪个是前任。然后我们从叶子节点走到树的根节点,删除所有子树仅包含k的节点,并在必要时更新后代指针。过去指向k 的后代指针现在将指向k的后继或前任,具体取决于缺少哪个子树。

与插入类似,这需要O (log M )的时间,因为我们必须遍历 trie 的每一层。

讨论

[编辑]

Willard 引入 x-fast tries 主要是为了介绍y-fast tries ,它提供相同的查询时间,同时仅使用O ( n ) 空间,并允许在O (log 日志 M )时间。

通过在各层级进行二分搜索之前使用指数搜索,并且不仅查询当前前缀x ,还查询其后继x + 1、x-fast tries 可以在O (log 日志 Δ ),其中Δ是查询值与其前任或后继值之间的差值。

参考

[编辑]

引用错误:在<references>标签中name属性为“PaperWillard”的参考文献没有在文中使用
引用错误:在<references>标签中name属性为“PaperKementsietsidis”的参考文献没有在文中使用
引用错误:在<references>标签中name属性为“PaperBose”的参考文献没有在文中使用

引用错误:在<references>标签中name属性为“NotesSchulz”的参考文献没有在文中使用

外部链接

[编辑]

[[Category:树结构]]