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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章