zoukankan      html  css  js  c++  java
  • 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
    ", 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;
    }
    
  • 相关阅读:
    Mathematica 计算矩阵的伴随矩阵
    教你如何在word中像LaTex那样打出漂亮的数学公式
    中国科学院大学2016年硕转博考试试题
    161024解答
    161023解答
    161020-1解答
    关于查询扩展版ESI高被引论文的说明
    [Tex学习笔记]让项目编号从4开始
    [Tex学习]WinEdit 常用软件快捷键
    最著名的数学家一般也是最著名的力学家
  • 原文地址:https://www.cnblogs.com/tztqwq/p/14149744.html
Copyright © 2011-2022 走看看