1. 程式人生 > >在平衡樹的海洋中暢遊(二)——Scapegoat Tree

在平衡樹的海洋中暢遊(二)——Scapegoat Tree

har 個數 bst 表示 檢查 png turn utc 重構

平衡樹的廣闊天地中,以Treap,Splay等為代表的通過旋轉來維護平衡的文藝平衡樹占了覺大部分。

然而,今天我們要講的Scapegoat Tree(替罪羊樹)就是一個特立獨行的平衡樹,它通過暴力重構來維護平衡,並且憑借著好寫,好調,常數小等特點十分有用。

記得g1n0st說過:

暴力即是優雅。

當然這裏說的暴力並不是指那種不加以思考的無腦的暴力,而是說用繁瑣而技巧性的工作可以實現的事,我用看似簡單的思想和方法,也可以達到近似於前者的空間復雜度和時間復雜度,甚至可以更優,而其中也或多或少的夾雜著一些" LessLess isis moremore "的思想在其中。

而替罪羊樹的重構就充滿了暴力美學

,一言不合就把整棵子樹拍扁重建,比如一棵這樣的樹:

技術分享圖片

而這樣很顯然,根的右子樹(以\(11\)為根)的子樹太深了,我們給它手動拍扁:

技術分享圖片

然後接回去就變成了:

技術分享圖片

至於如何有序,我們想一下二叉樹的中序遍歷,不是可以直接用線性時間把那個拍扁後的序列得出來了。

然後我們發現重構雖然可以維持樹的形狀,但它本身的較大的復雜度開銷也會引起TLE,因此我們要控制重構的次數

我們引入一個平衡因子\(alpha\),一般取值在\([0,7,0.8]\)之間,當一棵子樹的左右子樹的節點個數的較大值大於這棵子樹總的節點個數時,我們就把這棵子樹拍扁重構。

特殊地,當一個點被插入時,如果有多個要被重建的節點,那們我們就把以最高的(深度最小的)

節點(又叫替罪羊節點)為根的整棵子樹重構即可。

形象的理解一下:子樹要被重建不是我原來根的鍋,但是我就是被拍扁了還被重建了。果然不負替罪羊樹的稱號。

然後在刪除時,我們如果直接刪除由於沒有旋轉操作,大量的重構可能會引起TLE。

因此我們像線段樹的lazy標記一樣,在刪除一個點時直接打標記刪除即可。

然後又是板子題的CODE

#include<cstdio>
#include<cctype>
using namespace std;
typedef double DB;
const int N=100005;
const DB alpha=0.75;
struct Scapegoat
{
    int ch[2],size,fac,val;
    bool exi;
}node[N];//size是子樹總大小(算上被刪除的點),fac是實際上子樹總大小(不計被刪除的點),exi表示是否被刪除
int cur[N],mempol[N],cnt,tot,n,opt,x,rt,st;
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^‘-‘?1:-1;
    while (x=(x<<3)+(x<<1)+ch-‘0‘,isdigit(ch=tc())); x*=flag;
}
inline void write(int x)
{
    if (x<0) putchar(‘-‘),x=-x;
    if (x>9) write(x/10);
    putchar(x%10+‘0‘);
}
inline int max(int a,int b)
{
    return a>b?a:b;
}
inline bool balance(int now)//判斷是否平衡
{
    return (DB)node[now].fac*alpha>(DB)max(node[node[now].ch[0]].fac,node[node[now].ch[1]].fac);
}
inline void pushup(int now)
{
    node[now].size=node[node[now].ch[0]].size+node[node[now].ch[1]].size+1;
    node[now].fac=node[node[now].ch[0]].fac+node[node[now].ch[1]].fac+1;
}
inline void build(int now)
{
    node[now].ch[0]=node[now].ch[1]=0;
    node[now].size=node[now].fac=1;
}
inline void traversal(int now)//中序遍歷
{
    if (!now) return; traversal(node[now].ch[0]);
    if (node[now].exi) cur[++cnt]=now; else mempol[++tot]=now;
    traversal(node[now].ch[1]);
}
inline void setup(int l,int r,int &now)//將被拍扁的序列接成一棵樹,註意每次取端點保證深度最小
{
    int mid=l+r>>1; now=cur[mid];
    if (l==r) { build(now); return; }
    if (l<mid) setup(l,mid-1,node[now].ch[0]); else node[now].ch[0]=0;
    setup(mid+1,r,node[now].ch[1]); pushup(now);
}
inline void rebuild(int &now)//重構
{
    cnt=0; traversal(now);
    if (cnt) setup(1,cnt,now); else now=0;
}
inline void insert(int &now,int val)//插入,還是遵循BST的性質
{
    if (!now)
    {
        now=mempol[tot--]; node[now].val=val; node[now].exi=1;
        build(now); return;
    }
    ++node[now].size; ++node[now].fac;
    if (val<=node[now].val) insert(node[now].ch[0],val); else insert(node[now].ch[1],val);
}
inline void check(int now,int val)//在插入時檢查合法性,一言不和就重構
{
    int d=val<=node[now].val?0:1;
    while (node[now].ch[d])
    {
        if (!balance(node[now].ch[d])) { rebuild(node[now].ch[d]); break; }
        now=node[now].ch[d]; d=val<=node[now].val?0:1;
    }
}
inline int get_rk(int val)//得到排名,由於和許多的BST類似,就不再贅述
{
    int now=rt,rk=1;
    while (now)
    {
        if (val<=node[now].val) now=node[now].ch[0];
        else rk+=node[node[now].ch[0]].fac+node[now].exi,now=node[now].ch[1];
    }
    return rk;
}
inline int get_val(int rk)//得到排名為rk的樹數
{
    int now=rt;
    while (now)
    {
        if (node[now].exi&&node[node[now].ch[0]].fac+1==rk) return node[now].val;
        else if (node[node[now].ch[0]].fac>=rk) now=node[now].ch[0];
        else rk-=node[node[now].ch[0]].fac+node[now].exi,now=node[now].ch[1];
    }
}
inline void remove_rk(int &now,int rk)//刪除排名為rk的數
{
    if (node[now].exi&&node[node[now].ch[0]].fac+1==rk) { node[now].exi=0; --node[now].fac; return; }
    --node[now].fac; if (node[node[now].ch[0]].fac+node[now].exi>=rk) remove_rk(node[now].ch[0],rk);
    else remove_rk(node[now].ch[1],rk-node[node[now].ch[0]].fac-node[now].exi);
}
inline void remove_val(int val)//刪除值為val的數,註意如果實際上的點已經很少了也要重構
{
    remove_rk(rt,get_rk(val));
    if ((double)node[rt].size*alpha>node[rt].fac) rebuild(rt);
}
inline void init(void)
{
    for (register int i=100000;i>=1;--i)
    mempol[++tot]=i;
}
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    read(n); init();
    while (n--)
    {
        read(opt); read(x);
        switch (opt)
        {
            case 1:st=rt,insert(rt,x),check(st,x); break;
            case 2:remove_val(x); break;
            case 3:write(get_rk(x)),putchar(‘\n‘); break;
            case 4:write(get_val(x)),putchar(‘\n‘); break;
            case 5:write(get_val(get_rk(x)-1)),putchar(‘\n‘); break;
            case 6:write(get_val(get_rk(x+1))),putchar(‘\n‘); break;
        }
    }
    return 0;
}

——識替罪羊樹之算法乃吾生之幸也!

在平衡樹的海洋中暢遊(二)——Scapegoat Tree