• 平衡树


    Updating...

    前置芝士

    1. 二叉搜索树(Binary Search Tree,BST)

    普通平衡树

    模板:

    普通平衡树的基本操作可以参考上方模板

    1. 替罪羊树

    众所周知 每种平衡树都有一种操作来维护平衡。

    替罪羊树是暴力维护平衡的(

    替罪羊树要存 左右儿子,节点值,子树实际大小(前面的一般平衡树都要用),子树不实际大小(?)还有删除标记(后面解释)。

    struct Node
    {
    	int l,r,val;
    	int size,fact; // 实际大小:fact
    	bool exist; // exist : 是否存在
    }tzy[N];
    int cnt,root; // cnt : 节点数量    root : 根
    
    // 创建新节点
    inline void newnode(int &now,int val){now=++cnt; tzy[now].val=val; tzy[now].size=tzy[now].fact=1; tzy[now].exist=true;}
    

    1.1. 插入

    只要按 BST 插入方法插入即可,但是为了平衡还得在后面加一句判平衡。

    void ins(int &now,int val)
    {
    	if (!now){newnode(now,val); check(root,now); return ;} // check : 判平衡
    	++tzy[now].size; ++tzy[now].fact;
    	if (val<tzy[now].val) ins(tzy[now].l,val);
    	else ins(tzy[now].r,val);
    }
    

    1.2. 删除

    还记得前面节点里有维护「删除标记」吗?

    对,删除就是把节点打上标记。

    而「子树不实际大小」就是树中子树的大小,
    「子树实际大小」就是树中子树去掉打了标记的大小。

    同样,为了平衡还得在后面加一句判平衡。

    void del(int now,int val)
    {
    	if (tzy[now].exist&&tzy[now].val==val){tzy[now].exist=false; tzy[now].fact--; check(root,now); return ;}
    	tzy[now].fact--;
    	if (val<tzy[now].val) del(tzy[now].l,val);
    	else del(tzy[now].r,val);
    }
    

    !.! 判断树是否平衡并调整树

    插入和删除都提到了「判平衡」,但是「判平衡」怎么写呢?

    我们从根向要操作的节点找,如果找到了需要重构的节点,就暴力重构它的子树。

    判断是否平衡的条件是:

    当前节点的左子树或右子树的大小大于当前节点的大小 ( imes alpha),其中 (alpha) 是平衡因子,或
    当前节点为根的子树内被删除的节点数量 (>) 树大小(非实际)的 (30\%) .

    (alpha) 必须取 ([0.5,1]),一般取 (alphain[0.7,0.8]),最平常的是取 (alpha=0.75) .

    inline bool imbalence(int now){return (max(tzy[tzy[now].l].size,tzy[tzy[now].r].size)>tzy[now].size*alpha)||
                                          (tzy[now].size-tzy[now].fact>tzy[now].size*0.3);                      }
    

    所以总体的 check 大概是这样的:

    void check(int &now,int end)
    {
    	if (now==end) return;
    	if (imbalence(now)){rebuild(now); update(root,now); return ;}
    	if (tzy[end].val<tzy[now].val) check(tzy[now].l,end);
    	else check(tzy[now].r,end);
    }
    

    这里 rebuild 是重构,update 是更新数据(size)。

    update 非常的好写:

    void update(int now,int end)
    {
    	if (!now) return;
    	if (tzy[end].val<tzy[now].val) update(tzy[now].l,end);
    	else update(tzy[now].r,end);
    	tzy[now].size=tzy[tzy[now].l].size+tzy[tzy[now].r].size+1;
    }
    

    前面说过了,替罪羊树的维护平衡(重构)是暴力,替罪羊树的重构就是拉开(中序遍历)再拎起来。

    来个动图演示一下:
    rPeTqH.gif

    vector<int> v;
    void ldr(int now) // 拉开
    {
    	if (!now) return;
    	ldr(tzy[now].l);
    	if (tzy[now].exist) v.push_back(now);
    	ldr(tzy[now].r);
    }
    void lift(int l,int r,int &now) // 拎起来
    {
    	if (l==r){now=v[l]; tzy[now].l=tzy[now].r=0; tzy[now].size=tzy[now].fact=1; return ;}
    	int m=(l+r)>>1;
    	while ((l<m)&&(tzy[v[m]].val==tzy[v[m-1]].val)) --m;
    	now=v[m];
    	if (l<m) lift(l,m-1,tzy[now].l);
    	else tzy[now].l=0;
    	lift(m+1,r,tzy[now].r);
    	tzy[now].size=tzy[tzy[now].l].size+tzy[tzy[now].r].size+1; // (
    	tzy[now].fact=tzy[tzy[now].l].fact+tzy[tzy[now].r].fact+1;
    }
    void rebuild(int &now)
    {
    	v.clear(); ldr(now);
    	if (v.empty()){now=0; return ;}
    	lift(0,v.size()-1,now);
    }
    

    1.3. & 1.4. 查询值的排名 / 查询排名的值

    这些就按照普通 BST 写就行啦

    int getrank(int val)
    {
    	int now=root,rank=1;
    	while (now)
    	{
    		if (val<=tzy[now].val) now=tzy[now].l;
    		else {rank+=tzy[now].exist+tzy[tzy[now].l].fact; now=tzy[now].r;}
    	} return rank;
    }
    int getval(int rank)
    {
    	int now=root;
    	while (now)
    	{
    		if (tzy[now].exist&&tzy[tzy[now].l].fact+tzy[now].exist==rank) break;
    		else if (tzy[tzy[now].l].fact>=rank) now=tzy[now].l;
    		else {rank-=tzy[tzy[now].l].fact+tzy[now].exist; now=tzy[now].r;} // 这个 rank-=... 有主席树内味了
    	} return tzy[now].val;
    }
    

    1.5. & 1.6. 求前趋 / 求后继

    前趋和后继有个小技巧:
    前趋:getval(getrank(x)-1);
    后继:getval(getrank(x+1));

    inline int pre(int x){return getval(getrank(x)-1);}
    inline int nxt(int x){return getval(getrank(x+1));}
    

    实际时空比较

    普通平衡树:

    平衡树 代码长度 时间 空间
    替罪羊树 3.15KB 352ms 1.89MB

    Refence

  • 相关阅读:
    华为机试:字符串翻转
    华为机试:数字颠倒
    华为机试:字符个数统计
    华为机试:提取不重复的整数
    华为机试:取近视值
    华为机试:进制转换
    华为机试:字符串分隔
    华为机试:明明的随机数
    华为机试:字符串最后一个单词的长度
    网易:相反数
  • 原文地址:https://www.cnblogs.com/CDOI-24374/p/14110905.html
Copyright © 2020-2023  润新知