van Emde Boas 树 学习笔记
概述
一种数据结构科技,可在 \(O(\log\log V)\) 的复杂度内支持插入、删除、查询前驱后继、查询元素是否存在、查询最值(相当于阉割了有关排名操作的平衡树),其中 \(V\) 是值域。
定义
首先把值域补到 \(2\) 的整数幂,设 \(V=2^u\)。
维护一个桶表示元素是否存在。运用分块思想,将序列分为约 \(\sqrt V\) 块。为了保证分块后每一块还是 \(2\) 的整数幂,块长为 \(2^{\lfloor\frac u 2\rfloor}\),块数为 \(2^{\lceil\frac{u+1}2\rceil}\)。
此时这个结构可以看作一颗高 \(2\) 的树。其中,每一个叶节点都是规模更小的子结构,考虑继续对每一块分块,直到 \(u=1\),这就形成了一颗 \(\sqrt V\) 叉树。
对于每个节点,维护块大小、块里的最大值和最小值、儿子的号。此外还需要一个数组 summary 来维护每个儿子里是否有数,发现这也是一个桶,所以也用一个类似的结构维护。
struct node{
int u,sum,minn,maxn;
vector<int>son;
};
另外有一个性质,如果一个数成为了某个节点的最小值,就不在子树里处理它,这可以保证复杂度。叶节点比较特殊,只需要最小和最大值的信息。
辅助函数
int high(int u,int k){
return k>>(u>>1);
}
int low(int u,int k){
return k&((1<<(u>>1))-1);
}
int index(int u,int x,int y){
return x<<(u>>1)|y;
}
high 表示 \(k\) 在 \(pos\) 的第几个子节点;
low 表示 \(k\) 在 \(pos\) 的子节点的第几个;
index 则为第 \(x\) 个子节点的第 \(y\) 个在 \(pos\) 里是第几个。
建树
直接按照定义建出树。可以证明树的节点数为 \(O(V)\) 级别,建树的复杂度和空间复杂度也为 \(O(V)\)。
void build(int pos,int u){
tr[pos].minn=tr[pos].maxn=-1,tr[pos].u=u;
if(u==1)return;
tr[pos].sum=++cnt,build(cnt,(u+1)>>1);
for(int i=0;i<1<<((u+1)>>1);i++)tr[pos].son.push_back(++cnt),build(cnt,u>>1);
}
查询最值
直接返回,\(O(1)\)。
int front(int pos){
return tr[pos].minn;
}
int back(int pos){
return tr[pos].maxn;
}
查询元素是否存在
边界条件:如果元素等于最小或最大值,直接返回 \(1\)。否则,如果处于叶节点,那么元素不存在。
然后递归查询。
bool find(int pos,int k){
if(tr[pos].minn==k||tr[pos].maxn==k)return 1;
if(!tr[pos].u)return 0;
return find(tr[pos].son[high(pos,k)],low(pos,k));
}
复杂度证明:
首先,vEB 树的高为 \(\log\log V\)。可以这么想,\(u=\log V\),而 \(u\) 每一层都会减半。
而这个操作只会递归一次,相当于从根节点走到叶节点,复杂度 \(O(\log\log V)\)。其他操作如果只递归一次,复杂度也一样。
询问前驱后继
先讲后继。如果处于叶节点,可以 \(O(1)\) 判断。
如果节点里所有元素都比查询值大,返回最小值。
然后分两种情况:如果答案就在查询值所在的子节点内(也就是查询值小于这个子节点的最大值),递归进这个子节点查询。否则在 summary 中查询这个子节点后第一个有值的块,答案为这个块的最小值。
int next(int pos,int k){
if(!tr[pos].u)return k==0&&tr[pos].maxn==1?1:-1;
if(tr[pos].minn!=-1&&tr[pos].minn>k)return tr[pos].minn;
int temp=back(tr[pos].son[high(pos,k)]);
if(temp!=-1&&low(pos,k)<temp)return index(pos,high(pos,k),next(tr[pos].son[high(pos,k)],low(pos,k)));
temp=next(tr[pos].sum,high(pos,k));
return temp==-1?-1:index(pos,temp,front(tr[pos].son[temp]));
}
这是一个分支结构,只会递归一次。
前驱与后继大致对称。只是多了一行:如果查询不到上一个有值的块,也有可能前驱是最小值。
int prev(int pos,int k){
if(!tr[pos].u)return k==1&&tr[pos].minn==0?0:-1;
if(tr[pos].maxn!=-1&&tr[pos].maxn<k)return tr[pos].maxn;
int temp=front(tr[pos].son[high(pos,k)]);
if(temp!=-1&&low(pos,k)>temp)return index(pos,high(pos,k),prev(tr[pos].son[high(pos,k)],low(pos,k)));
temp=prev(tr[pos].sum,high(pos,k));
return temp==-1?(tr[pos].minn!=-1&&tr[pos].minn<k?tr[pos].minn:-1):index(pos,temp,back(tr[pos].son[temp]));
}
插入
若节点没有数,更新最大和最小值后返回。
如果插入的数比最小值小,此时插入的数不用继续递归,而原来的最小值需要进入到子树中,交换一下即可。
如果插入的值所属的子节点没有数,就需要更新 summary。然后递归进子树。最后更新最大值。
void insert(int pos,int k){
if(tr[pos].minn==-1){
tr[pos].minn=tr[pos].maxn=k;
return;
}
if(tr[pos].minn>k)swap(tr[pos].minn,k);
if(tr[pos].u){
if(front(tr[pos].son[high(pos,k)])==-1)insert(tr[pos].sum,high(pos,k));
insert(tr[pos].son[high(pos,k)],low(pos,k));
}
if(tr[pos].maxn<k)tr[pos].maxn=k;
}
这里看似可能递归两次,其实如果执行了第一次递归,说明这个子节点没有数,第二次递归就会 \(O(1)\) 判掉。
删除
首先判掉只有一个数和叶节点的情况。
如果删除的是最小值,要把删除的值和最小值都更新为次小值,即最小的有值的子节点的最小值。
然后在子树里删除。如果删除后这个子节点空了,就在 summary 中删除。
更新最大值分两种情况:若删之后子节点空了,新的最大值不在这个节点内,赋值为最大的有值的子节点的最大值。如果查找不到则最大值和最小值一样。否则只需要在这个子节点里找。
复杂度分析同插入。
void erase(int pos,int k){
if(tr[pos].minn==tr[pos].maxn){
tr[pos].minn=tr[pos].maxn=-1;
return;
}
if(!tr[pos].u){
tr[pos].minn=tr[pos].maxn=!k;
return;
}
int temp;
if(tr[pos].minn==k)temp=front(tr[pos].sum),tr[pos].minn=k=index(pos,temp,front(tr[pos].son[temp]));
erase(tr[pos].son[high(pos,k)],low(pos,k));
if(front(tr[pos].son[high(pos,k)])==-1){
erase(tr[pos].sum,high(pos,k));
if(tr[pos].maxn==k){
temp=back(tr[pos].sum);
if(temp==-1)tr[pos].maxn=tr[pos].minn;
else tr[pos].maxn=index(pos,temp,back(tr[pos].son[temp]));
}
}
else if(tr[pos].maxn==k)tr[pos].maxn=index(pos,high(pos,k),back(tr[pos].son[high(pos,k)]));
}
优化
当前节点的大小不超过 \(64\) 时,可以不用继续分块而是直接用一个 unsigned long long 维护,减少了很多常数。
如果值域很大,可以对 vEB 树动态开点,这也被称为 RS-vEB 树。
struct node{
int sum,minn,maxn;
unsigned long long v;
unordered_map<int,int>son;
node(){
minn=maxn=-1;
}
};
template<int maxn>struct vEB{
node tr[maxn];
int cnt;
int high(int u,int k){
return k>>(u>>1);
}
int low(int u,int k){
return k&((1<<(u>>1))-1);
}
int index(int u,int x,int y){
return x<<(u>>1)|y;
}
int front(int pos,int u){
return u<=6?(tr[pos].v?__builtin_ctzll(tr[pos].v):-1):tr[pos].minn;
}
int back(int pos,int u){
return u<=6?(tr[pos].v?63-__builtin_clzll(tr[pos].v):-1):tr[pos].maxn;
}
bool find(int &pos,int u,int k){
if(!pos)pos=++cnt;
if(u<=6)return tr[pos].v>>k&1;
if(tr[pos].minn==k||tr[pos].maxn==k)return 1;
return find(tr[pos].son[high(u,k)],u>>1,low(u,k));
}
int prev(int &pos,int u,int k){
if(!pos)pos=++cnt;
if(u<=6)return tr[pos].v&((1ull<<k)-1)?63-__builtin_clzll(tr[pos].v&((1ull<<k)-1)):-1;
if(tr[pos].maxn!=-1&&tr[pos].maxn<k)return tr[pos].maxn;
int temp=front(tr[pos].son[high(u,k)],u>>1);
if(temp!=-1&&low(u,k)>temp)return index(u,high(u,k),prev(tr[pos].son[high(u,k)],u>>1,low(u,k)));
temp=prev(tr[pos].sum,(u+1)>>1,high(u,k));
return temp==-1?(tr[pos].minn!=-1&&tr[pos].minn<k?tr[pos].minn:-1):index(u,temp,back(tr[pos].son[temp],u>>1));
}
int next(int &pos,int u,int k){
if(!pos)pos=++cnt;
if(u<=6)return tr[pos].v>>(k+1)?k+1+__builtin_ctzll(tr[pos].v>>(k+1)):-1;
if(tr[pos].minn!=-1&&tr[pos].minn>k)return tr[pos].minn;
int temp=back(tr[pos].son[high(u,k)],u>>1);
if(temp!=-1&&low(u,k)<temp)return index(u,high(u,k),next(tr[pos].son[high(u,k)],u>>1,low(u,k)));
temp=next(tr[pos].sum,(u+1)>>1,high(u,k));
return temp==-1?-1:index(u,temp,front(tr[pos].son[temp],u>>1));
}
void insert(int &pos,int u,int k){
if(!pos)pos=++cnt;
if(u<=6){
tr[pos].v|=1ull<<k;
return;
}
if(tr[pos].minn==-1){
tr[pos].minn=tr[pos].maxn=k;
return;
}
if(tr[pos].minn>k)swap(tr[pos].minn,k);
if(front(tr[pos].son[high(u,k)],u>>1)==-1)insert(tr[pos].sum,(u+1)>>1,high(u,k));
insert(tr[pos].son[high(u,k)],u>>1,low(u,k));
if(tr[pos].maxn<k)tr[pos].maxn=k;
}
void erase(int &pos,int u,int k){
if(!pos)pos=++cnt;
if(u<=6){
tr[pos].v^=tr[pos].v&1ull<<k;
return;
}
if(tr[pos].minn==tr[pos].maxn){
tr[pos].minn=tr[pos].maxn=-1;
return;
}
int temp;
if(tr[pos].minn==k)temp=front(tr[pos].sum,(u+1)>>1),tr[pos].minn=k=index(u,temp,front(tr[pos].son[temp],u>>1));
erase(tr[pos].son[high(u,k)],u>>1,low(u,k));
if(front(tr[pos].son[high(u,k)],u>>1)==-1){
erase(tr[pos].sum,(u+1)>>1,high(u,k));
if(tr[pos].maxn==k){
temp=back(tr[pos].sum,(u+1)>>1);
if(temp==-1)tr[pos].maxn=tr[pos].minn;
else tr[pos].maxn=index(u,temp,back(tr[pos].son[temp],u>>1));
}
}
else if(tr[pos].maxn==k)tr[pos].maxn=index(u,high(u,k),back(tr[pos].son[high(u,k)],u>>1));
}
};
[[数据结构]]

浙公网安备 33010602011771号