Van Emde Boas树

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

**结构总览**
一棵 vEB 树的每个节点包含以下资料：- u：纪录该节点管理的键值集合范围，范围为 。- min：节点目前储存的最小元素。若节点为空，则设为NIL标记为空。- max：节点目前储存的最大元素。若节点为空，则设为NIL标记为空。- cluster：一个大小为  的阵列，其中每个元素是指向簇的指标，每个簇是一个子 vEB 树，第  个簇负责管理范围的资料，在该簇中会被重新映射到  储存。- summary：一棵辅助 vEB 树，追踪哪些簇有资料。当且仅当 mono|T.clusteri 非空时 T.summary 才会包含值 。
值得注意的是，若  是最小元素，直接记录在T.min，不会被记录在其他地方。
若节点为空，设定 1=T.min = T.max = NIL，所有找寻操作遇 NIL 都视作「不存在」，部分实作会采用 -1 和 u 来代替。

**表示法**
在后续的演算法描述中，我们会用到以下函数，设整体键值范围为 ，定义:
说明：
#  是每个簇的大小；
#  给出  在哪个簇（簇编号从 0 开始）；
#  给出  在该簇内，范围为  内的键值（编号也从 0 开始）；
#  则把簇编号  与簇内键值  重组为全局键值。
可以观察到：
。

**基本操作**
以下为vEB 树支援的一些操作:

**插入**
将值 x 插入到 vEB 树 T 的操作 insert(T, x) 过程如下：
#  如果 x 和 T.min 或 T.max 相等，操作结束。
#  如果 T 为空，则设定 1=T.min = T.max = x，操作结束。
#  如果 x  T.max，则设定 1=T.max = x。
#  最后，把 x 插入到负责 x 的簇 mono|T.clusterhigh(x) 中。如果 mono|T.clusterhigh(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.max then
T.max = x  // 更新最大点
i = high(x)
Insert(T.clusteri, low(x))
if T.clusteri.min == T.clusteri.max then
Insert(T.summary, i)
end
此过程的效率关键在于，将元素插入到空的 vEB 树只需  时间，然后在大小为 （即  位二进制数）的子树上递回处理。因此，即使算法有时会进行两次递回调用，这仅在第一次递回调用进入空子树时发生。可以得出这运行时间的递回关系是  ，其解为 。

**查询后继**
查询后继的过程如下：
# 如果 mono|x T.max then
return T.max
if x ≤ T.min then  // 无前驱数
return NIL
i = high(x)

if low(x) > T.clusteri.min then  // 前驱在这个簇
s = FindPrev(T.clusteri, 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.clusterj.max)  // 与高位合并
end

**删除**
从vEB树中删除节点是最复杂的操作。呼叫 Delete(T, x) 来删除vEB树T中的值x，其运作方式如下：
# 如果 1=T.min = T.max = x，则x是树中唯一的元素。我们将 1=T.min = NIL 和 1=T.max = NIL 来表示树为空。
# 否则，如果 1=x == T.min，则需要找到vEB树中第二小的值y，从其当前位置删除它，并设定 1=T.min=y。第二小的值y是 mono|T.clusterT.summary.min.min，因此可以在  时间内找到。我们从包含y的子树中删除它。
# 如果 x≠T.min 且 x≠T.max，则从包含x的子树 mono|T.clusteri 中删除x。
# 如果 1=x == T.max，则需要找到vEB树中第二大的值y并设定 1=T.max=y。首先按照前一种情况删除x。值y要么是 T.min，要么是 mono|T.clusterT.summary.max.max，因此可以在  时间内找到。
在上述任何情况下，如果我们从子树 mono|T.clusteri 中删除最后一个元素x或y，则还需要从 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.clusterj.min)  // 新min = 树中存在的最小值
Delete(T.clusterj, T.clusterj.min)  // 从子树中删除，因为T.min不应在子树中出现
return
// 分解高位和低位
i = high(x)  // 计算簇索引
// 递回删除
Delete(T.clusteri, low(x)) 
// 处理空子树
if T.clusteri 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.clusterj.max)  // 合并
end
同样，此程序的效率取决于从仅包含一个元素的vEB树中删除只需  时间。特别是，只有在删除前 x 是 mono|T.clusteri 中唯一元素时，才会递回删除 T.summary 中对应的元素。

**实际实现**
实际上并不需要假设  必须是整数。运算 low(x) 和 high(x) 可以分别替换为只取 x 的高位  位元和低位  位元。在大部分情况下，这都比除法或取余运算更高效。
在实际实现中，特别是在具有「位移 k 位」和「寻找首位零」指令的机器上，当 m 达到字长（或其小倍数）时，可以切换为使用位阵列来进一步提升效能。由於单一字长上的所有操作都是常数时间，这不会影响渐进效能，但能避免多数指标储存和多次指标解引用，从而显著节省实际的时间和空间。
vEB 树的一个优化是舍弃空子树。这使得 vEB 树在包含大量元素时非常紧凑，因为只有在需要添加元素时才会创建子树。最初，每添加一个元素会创建约  个新树，总共包含约  个指标。随着树的增长，越来越多的子树会被重复使用，尤其是较大的子树。
上述实现使用指标，并占用总空间 ，与键值空间的大小成正比。这可以通过以下方式理解：递回关系为 。解此递回关系会得到 。也可以通过数学归纳法证明 。

**参考资料**

**参见**
 平衡树- 树 (资料结构)