leafy tree 结构, 大概是 k 叉树的非叶节点都有 k 个子节点, 比如线段树就是 leafy 的。
可以用 leafy tree 结构实现加权平衡树, 大概是叫做 WBLT(Weight Balanced Leafy Tree)。这个 WBLT 要维护的原始信息全都存储在叶节点上,对于每个插入进 WBLT 的原始信息 ai,每个叶节点都有 value 和 size 两种键值, 对于 ai 对应的叶节点, 有 value = ai, size = 1; 对于非叶节点, 其 value 等于其右子节点的 value, 其 size 等于其左子节点和其右子节点的 size 之和。(其实非叶节点的 value 还可定义为左子节点的 value, 不同的定义会造成具体操作的实现不同,以下都默认是右子节点的 value)
对于 WBLT 中序遍历形成的序列中叶子节点的相对顺序,要保证是从小到大排序后的相对顺序,以下暂且称其为 WBLT 性质。这样, 一个子树的根节点的 value 就是其子树中 value 最大的叶节点的 value,查找什么的操作就都容易写了。
一般来说用指针写比较清爽,具体来说就是 me->ls->ls->size
和 t[t[t[me].ls].ls].siz
的区别。对于指针回收问题, 一般写个内存池。
节点这么写:
struct node{
int siz, val;
node *ls, *rs;
node( int s, int v, node *a, node *b) : siz(s),val(v),ls(a),rs(b) {}
node () {}
} *root, *null, t[100], *pool[100];
int cnt = 0; // pool 用
int main()
{
null = new node(0, 0, NULL, NULL); // 因为直接访问空指针会报错
root = new node(1, INF, null, null); // 初始时树为哨兵节点
for(int i=0; i<100; ++i) pool[i] = &t[i]; // 内存池初始化
return 0;
}
这样, 创建新节点的操作就可以写成:
#define newnode(s, v, a, b) (&(*pool[cnt++] = node(s, v, a, b)))
简洁!赋值的同时还返回了地址 ovo。
回收指针的时候直接 pool[--cnt] = /*pointer name*/
就行了。
插入与删除操作:
不难发现由于 WBLT 非叶节点的 value 的性质, 很容易找到插入与删除的位置, 此时插入与删除的区别就是新建节点与删除节点的区别了。 不难发现对于每次插入操作, 都要新建两个节点,故 WBLT 比起常见的那种所有节点都存储原始信息的平衡树, 要消耗两倍的空间。
旋转平衡:
这里只介绍单旋, 听人说没人卡……我猜单旋是可以卡的, 其复杂度证明我没见过, 但双旋的复杂度是有证明的。对于一个节点,其左儿子与右儿子的 size 差距过大时, 要旋转。具体来说:
#define ratio 4
// 我看其他人的写法都是定义的 4...
#define merge(a, b) newnode(a->siz+b->siz, b->val, a, b)
// 即对于特定的左子节点和右子节点生成一个父亲
inline void maintain(register node * me)
{
if(me->ls->siz > me->rs->siz * ratio)
me->rs = merge(me->ls->rs, me->rs), st[--cnt] = me->ls, me->ls =me->ls->ls;
if(me->rs->siz > me->ls->siz * ratio)
me->ls = merge(me->ls, me->rs->ls), st[--cnt] = me->rs, me->rs =me->rs->rs;
}
即, 直接把过重的儿子的一部分拿到另一个儿子上, 然而我还是不会证复杂度……
这个旋转操作挺有启发性的, 对于那些 treap,splay 一类的平衡树的单旋操作, 也可以看成是像这样的 “重量让渡”。
忘了说了, 很容易证明旋转后整棵树还是满足 WBLT 性质的。
又忘了说了,在叶子节点及叶子节点的父亲节点处旋转会错误, 但是由于 if 语句的存在, 在这两处不会旋转。
贴个普通平衡树的代码吧:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int N=1e5+233;
#define newnode(s, v, a, b) (&(*pool[cnt++] = node(s, v, a, b)))
#define merge(a, b) newnode(a->siz+b->siz, b->val, a, b)
#define upd(me) if(me->ls->siz) me->siz=me->ls->siz+me->rs->siz, me->val=me->rs->val
#define ratio 4
struct node{
int siz,val;
node *ls, *rs;
node(int s, int v, node *a, node *b) : siz(s), val(v), ls(a), rs(b) {}
node() {}
} *root, *null, t[N<<1], *pool[N<<1];
int n, cnt;
inline void maintain(register node *me) {
if(me->ls->siz > me->rs->siz*ratio)
me->rs=merge(me->ls->rs, me->rs), pool[--cnt]=me->ls, me->ls=me->ls->ls;
if(me->rs->siz > me->ls->siz*ratio)
me->ls=merge(me->ls, me->rs->ls), pool[--cnt]=me->rs, me->rs=me->rs->rs;
}
void ins(int x,node *me) {
if(me->siz == 1) me->ls = newnode(1, min(x,me->val), null, null), me->rs = newnode(1, max(x,me->val), null, null);
else ins(x, x>me->ls->val ? me->rs : me->ls);
upd(me); maintain(me);
}
void era(int x,node *me) {
if(me->ls->siz==1 && me->ls->val==x)
pool[--cnt]=me->ls, pool[--cnt]=me->rs, *me=*me->rs;
else if(me->rs->siz==1 && me->rs->val==x)
pool[--cnt]=me->rs, pool[--cnt]=me->ls, *me=*me->ls;
else era(x, x>me->ls->val ? me->rs : me->ls);
upd(me); maintain(me);
}
int fid(int x,node *me) {
if(me->siz == 1) return me->val;
return x>me->ls->siz ? fid(x-me->ls->siz, me->rs) : fid(x, me->ls);
}
int rnk(int x,node *me) {
if(me->siz == 1) return 1;
return x>me->ls->val ? me->ls->siz + rnk(x, me->rs) : rnk(x, me->ls);
}
int main()
{
null = new node(0, 0, NULL, NULL);
root = new node(1,INF,null,null);
for(int i=0;i<(N<<1);++i) pool[i]=&t[i];
scanf("%d",&n);
int opt,x;
while(n--)
{
scanf("%d%d",&opt,&x);
if(opt==1) ins(x, root);
else if(opt==2) era(x, root);
else if(opt==3) printf("%d
", rnk(x, root));
else if(opt==4) printf("%d
", fid(x, root));
else if(opt==5) printf("%d
", fid(rnk(x, root)-1, root));
else printf("%d
", fid(rnk(x+1, root), root));
}
return 0;
}