跳转到内容

van Emde Boas树

本页使用了标题或全文手工转换
维基百科,自由的百科全书

van Emde Boas树
类型非二元
发明时间1975年
发明者Peter van Emde Boas
大O符号表示的时间复杂度
算法 平均 最差
搜索
插入
删除
大O符号表示的空间复杂度
空间

van Emde Boas树(或称vEB树或van Emde Boas优先队列)是一个在计算机科学中的数据结构,也是一种关系数组,也是一种,由荷兰计算机科学家 Peter van Emde Boas[1]领导的团队于 1975 年发明,可以存储键值范围在 m 位以内的二进制整数,也就是 是树中可以存储的最大数字时,它可以在 时间内执行所有种类的基本操作(假设对 的位操作可以在常量时间内执行),也就是 时间。参数不要与树中存储的实际元素数量混淆,其他树数据结构的性能通常透过该数量来衡量。 标准vEB树的空间效率不够。例如,用于存储 32 比特整数(即当 ),它需要 个存储位。然而,具有相似的时间效率和空间效率的数据结构可以使用 空间(当 𝑛 是存储元素的数量),但是也可以修改vEB树让它只需要 空间。[2][3][4][5]

结构总览

[编辑]

一棵 vEB 树的每个节点包含以下资料:

  • u:纪录该节点管理的键值集合范围,范围为
  • min:节点目前存储的最小元素。若节点为空,则设为NIL标记为空。
  • max:节点目前存储的最大元素。若节点为空,则设为NIL标记为空。
  • cluster:一个大小为 的数组,其中每个元素是指向簇的指针,每个簇是一个子 vEB 树,第 个簇负责管理范围的资料,在该簇中会被重新映射到 存储。
  • summary:一棵辅助 vEB 树,追踪哪些簇有资料。当且仅当 T.cluster[i] 非空时 T.summary 才会包含值

值得注意的是,若 是最小元素,直接记录在T.min,不会被记录在其他地方。

若节点为空,设置 T.min = T.max = NIL,所有找寻操作遇 NIL 都视作“不存在”,部分实现会采用 -1u 来代替。 [2][3][4]

表示法

[编辑]

在后续的算法描述中,我们会用到以下函数,设整体键值范围为 ,定义:

说明:

  1. 是每个簇的大小;
  2. 给出 在哪个簇(簇编号从 0 开始);
  3. 给出 在该簇内,范围为 内的键值(编号也从 0 开始);
  4. 则把簇编号 与簇内键值 重组为全局键值。

可以观察到: [4][5]

基本操作

[编辑]

以下为vEB 树支持的一些操作:[2][3][4]

操作 描述 时间复杂度
插入 在树中插入一个新值
查询后继 给定一个数,查找下一个数
查询前驱 给定一个数,查找上一个数
删除 在树中删除一个值

插入

[编辑]

将值 x 插入到 vEB 树 T 的操作 insert(T, x) 过程如下:

  1. 如果 xT.minT.max 相等,操作结束。
  2. 如果 T 为空,则设置 T.min = T.max = x,操作结束。
  3. 如果 x < T.min,则将 T.minx 交换,接下来的操作会把旧的T.min插入。
  4. 如果 x > T.max,则设置 T.max = x
  5. 最后,把 x 插入到负责 x 的簇 T.cluster[high(x)] 中。如果 T.cluster[high(x)] 之前为空,则同时将 high(x) 插入到 T.summary 中。

代码如下:

function Insert(T, x)
    if T.min == x || T.max == x then // x 已經被插入了
        return
    if T.min == NIL then // T 是空樹
        T.min = T.max = x; 
        return
   
    if x < T.min then
        swap(x, T.min) // 更新最小點,插入舊的最小點
    if x > T.max then
        T.max = x  // 更新最大點
    i = high(x)
    Insert(T.cluster[i], low(x))
    if T.cluster[i].min == T.cluster[i].max then
        Insert(T.summary, i)
end

此过程的效率关键在于,将元素插入到空的 vEB 树只需 时间,然后在大小为 (即 位二进制数)的子树上递归处理。因此,即使算法有时会进行两次递归调用,这仅在第一次递归调用进入空子树时发生。可以得出这运行时间的递归关系是 ,其解为 [2][3][4]

查询后继

[编辑]

查询后继的过程如下:

  1. 如果 x<T.min,则搜索结束,回传 T.min。如果 x≥T.max,则后继元素不存在,返回NIL。
  2. 否则,计算 i=high(x) 。如果 x < T.cluster[i].max,则要找的值位于 T.cluster[i] 中,因此在 T.cluster[i]递归搜索。
  3. 否则,在 T.summary 中搜索值 i 的后继,得到包含大于 x 元素的第一个簇的索引 j
  4. 然后,算法返回 T.cluster[j].min。在簇找到的元素需要与高位组合以形成完整的后继元素。

代码如下:

function FindNext(T, x)
    if x < T.min then
        return T.min
    if x ≥ T.max then // 無後繼數
        return u
    i = high(x)
    
    if low(x) < T.cluster[i].max then // 後繼在這個簇中
        return index(i,FindNext(T.cluster[i], low(x))) 
    
    j = FindNext(T.summary, i)  // 後繼在其他簇,搜尋輔助vEB樹
    if j == NIL then  // 檢查是否存在後繼簇
        return NIL
   else
        return index(j, T.cluster[j].min)  // 與高位合併
end

注意,在任何情况下,这个函数本身执行 的工作,然后可能在大小为 的簇上递归处理。和之前的递推关系一样,所以复杂度仍是 [2][3][4]

查询前驱

[编辑]

查询前驱和查询后继的方式大致相似,只有部分地方修改,重点在T.min 也有可能是前驱,但不在子树的任何地方。[2][3][4]

function FindPrev(T, x)
   if x > T.max then
       return T.max
   if x ≤ T.min then  // 無前驅數
       return NIL
   i = high(x)
   
   if low(x) > T.cluster[i].min then  // 前驅在這個簇
       s = FindPrev(T.cluster[i], low(x))
       if s == NIL then // 前驅在T.min
           return T.min
       return index(i,s)  // 與高位合併
   j = FindPrev(T.summary, i)  // 前驅在其他子樹,搜尋輔助vEB樹
   if j == NIL then  // 檢查是否存在前驅子樹
        return NIL
   else
       return index(j, T.cluster[j].max)  // 與高位合併
end

删除

[编辑]

从vEB树中删除节点是最复杂的操作。调用 Delete(T, x) 来删除vEB树T中的值x,其运作方式如下:

  1. 如果 T.min = T.max = x,则x是树中唯一的元素。我们将 T.min = NILT.max = NIL 来表示树为空。
  2. 否则,如果 x == T.min,则需要找到vEB树中第二小的值y,从其当前位置删除它,并设置 T.min=y。第二小的值yT.cluster[T.summary.min].min,因此可以在 时间内找到。我们从包含y的子树中删除它。
  3. 如果 x≠T.minx≠T.max,则从包含x的子树 T.cluster[i] 中删除x
  4. 如果 x == T.max,则需要找到vEB树中第二大的值y并设置 T.max=y。首先按照前一种情况删除x。值y要么是 T.min,要么是 T.cluster[T.summary.max].max,因此可以在 时间内找到。

在上述任何情况下,如果我们从子树 T.cluster[i] 中删除最后一个元素xy,则还需要从 T.summary 中删除i

代码如下:

function Delete(T, x)
   // 刪除最後一個元素
   if T.min == T.max == x then   
       T.min = NIL      // 標記空樹
       T.max = NIL 
       return
   // 刪除當前最小值
   if x == T.min then 
       j = T.summary.min          // 通過輔助樹找最小簇索引
       T.min = x = index(j, T.cluster[j].min)  // 新min = 樹中存在的最小值
       Delete(T.cluster[j], T.cluster[j].min)  // 從子樹中刪除,因為T.min不應在子樹中出現
      return
   // 分解高位和低位
   i = high(x)  // 計算簇索引
   // 遞迴刪除
   Delete(T.cluster[i], low(x)) 
   // 處理空子樹
   if T.cluster[i] is empty then  
       Delete(T.summary, i)         // 從輔助樹刪除該子樹索引
   // 刪除的是當前最大值
   if x == T.max then
       if T.summary is empty then      // 沒有其他子樹
           T.max = T.min        // 只剩min值
       else
           j = T.summary.max //找最大值
           T.max = index(j, T.cluster[j].max)  // 合併
end

同样,此程序的效率取决于从仅包含一个元素的vEB树中删除只需 时间。特别是,只有在删除前 xT.cluster[i] 中唯一元素时,才会递归删除 T.summary 中对应的元素。[2][3][4]

实际实现

[编辑]

实际上并不需要假设 必须是整数。运算 low(x)high(x) 可以分别替换为只取 x 的高位 比特和低位 比特。在大部分情况下,这都比除法取余运算更高效。

在实际实现中,特别是在具有“位移 k 位”和“查找首位零”指令的机器上,当 m 达到字长(或其小倍数)时,可以切换为使用位数组来进一步提升性能。由于单一字长上的所有操作都是常量时间,这不会影响渐进性能,但能避免多数指针存储和多次指针解引用,从而显著节省实际的时间和空间。

vEB 树的一个优化是舍弃空子树。这使得 vEB 树在包含大量元素时非常紧凑,因为只有在需要添加元素时才会创建子树。最初,每添加一个元素会创建约 个新树,总共包含约 个指针。随着树的增长,越来越多的子树会被重复使用,尤其是较大的子树。 上述实现使用指针,并占用总空间 ,与键值空间的大小成正比。这可以通过以下方式理解:递归关系为 。解此递归关系会得到 。也可以通过数学归纳法证明 [2][3][4]

参考资料

[编辑]
  1. ^ Prof.dr Peter van Emde Boas | Institute for Logic, Language and Computation. www.illc.uva.nl. [2025-04-06]. 
  2. ^ 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 van Emde Boas, P.; Kaas, R.; Zijlstra, E. Design and implementation of an efficient priority queue. Mathematical systems theory. 1976-12-01, 10 (1) [2025-04-06]. ISSN 1433-0490. doi:10.1007/BF01683268 (英语). 
  3. ^ 3.0 3.1 3.2 3.3 3.4 3.5 3.6 3.7 Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein. Introduction to Algorithms, Third Edition. MIT Press, 2009. ISBN 978-0-262-53305-8. Chapter 20: The van Emde Boas tree, pp. 531–560.
  4. ^ 4.0 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 RuntimeErr. 你所不了解的数据结构-van Emde Boas 树. 洛谷专栏. 2021-06-27 [2025-04-06] (中文(中国大陆)). 
  5. ^ 5.0 5.1 Van Emde Boas Tree | Set 1 | Basics and Construction. GeeksforGeeks. 2019-08-02 [2025-04-27] (美国英语). 

参见

[编辑]