WBLT初步

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->sizet[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\n", rnk(x, root));
		else if(opt==4) printf("%d\n", fid(x, root));
		else if(opt==5) printf("%d\n", fid(rnk(x, root)-1, root));
		else printf("%d\n", fid(rnk(x+1, root), root));
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章