1. 程式人生 > >平衡樹學習筆記(4)-------替罪羊樹

平衡樹學習筆記(4)-------替罪羊樹

替罪羊樹

上一篇:平衡樹學習筆記(3)-------Splay

替罪羊樹可以說是最暴力的平衡樹

但卻跑的很快

有多暴力?

不是一條鏈影響複雜度嗎?

暴力給你拍到一個vector裡去(沒錯,整棵樹暴力拍扁)

再重新建樹,建出的樹像線段樹那樣二分建來保證平衡

可謂是要多暴力有多暴力

在樹套樹上也有一些優勢

\(\color{#9900ff}{定義}\)

const double eps=0.75;
struct node
{
    int val,siz,cov;
    bool exist;
    node *ch[2];
    void upd()
    {
        siz=ch[0]->siz+ch[1]->siz+exist;
        cov=ch[0]->cov+ch[1]->cov+1;
    }
    bool nb(){ return ((ch[0]->cov>cov*eps+5)||(ch[1]->cov>cov*eps+5)); }
    int rk() { return ch[0]->siz+exist;}
};

不同的是,替罪羊樹的刪除不是真正的刪除,只是打個標記罷了

但是,要注意的是,如果被標記(假裝刪除)的點在某個子樹中,而現在要把那個子樹拍扁

則這個點就是真的被刪除了qwq,重構的時候就不再把它加入新樹了,因為沒必要qwq

所以,val記錄權值,siz記錄實際大小,cov記錄節點個數

exist就是標記,判斷此節點是否存在

剛剛說它維護平衡的方式就是拍扁重構

但總不能每次都把所有拍扁重構

條件就是,左/右子樹大小大於自己大小*平衡因子

這個平衡因子也是比較玄學,一般在0.7---0.85即可吧qwq

nb的意思是。。。need-bong,判斷是否需要拍扁,不,是被拍扁QAQ

\(\color{#9900ff}{基本操作}\)

1、travel

travel,顧名思義,旅行

讓樹上的節點去vector旅行qwq(<-----瞎bb)

說難聽點就是被拍扁。。。。。。

不過,為了重構便捷,還記得第一節說的嗎

平衡樹的中序遍歷可是有序的!

所以,按照中序遍歷拍扁,就方便重構了qwq

//vector要傳地址(別告訴我不知道為什麼)
void travel(nod o,vector<nod> &v)
{
    //到空節點不用管
    if(o==null) return;
    //中序遍歷左根右
    travel(o->ch[0],v);
    //放進vector之前,判斷是否存在, 不存在就不用管了,真正意義上把它刪除
    if(o->exist) v.push_back(o);
    //else這一行各位陣列dalao可以忽略,它的左右就是回收刪的節點放入記憶體池,節省空間利用
    else ts[top++]=o;
    travel(o->ch[1],v);
}

2、divide

別問我為啥叫divide,也許是因為二分建樹吧qwq

nod divide(vector<nod> &v,int l,int r)
{
    //二分邊界(因為[l,r)左閉右開)
    if(l>=r) return null;
    int mid=(l+r)>>1;
    //mid給自己,[l,mid-1]給左孩子,[mid+1,r)給右孩子
    nod o=v[mid];
    o->ch[0]=divide(v,l,mid);
    o->ch[1]=divide(v,mid+1,r);
    //別忘維護性質
    o->upd();
    return o;
}

3、rebuild

這就是重構,說白了就是把前兩個聯絡在一起

void rebuild(nod &o)
{
    static vector<nod> v;
    v.clear();
    travel(o,v);
    //左閉右開不要忘
    o=divide(v,0,v.size());
}

基本操作已經完成,夠暴力吧qwq

\(\color{#9900ff}{其它操作}\)

1、插入

由於涉及遞迴,而且有些不同,所以分成了兩個函式來寫

插入節點畢竟會改變樹的樣子

所以要考慮拍扁

那麼,現在問題來了

我們既要保證樹的平衡,又要保證複雜度,怎麼辦呢?

我們找滿足nb的最淺的那個重構

這樣既可以保證樹的平衡,又不會因為太淺而影響複雜度(完美؏؏☝ᖗ乛◡乛ᖘ☝؏؏)

//返回的是最淺的nb(牛逼)點的地址
nod *ins(nod &o,int k)
{
    if(o==null)
    {
        //如果為空則建立新節點
        //注意返回的是指標的地址
        o=newnode(k);
        return &null;
    }
    //插入的點在當前點的子樹內
    //而這個點是存在的
    //所以siz和cov都++
    o->siz++,o->cov++;
    //遞迴像某個子樹找
    nod *p=ins(o->ch[k>=o->val],k);
    //先讓p等於子樹中的nb點
    //如果當前點是nb點,顯然o比p淺,所以p=&o
    if(o->nb()) p=&o;
    return p;
}
void ins(int k)
{
    nod *p=ins(root,k);
    //只要不空說明有nb點,就拍扁重構
    if(*p!=null) rebuild(*p);
}

2、刪除

因為我們只有拍扁重構的操作

對於刪除實在是不方便

所以改了一下刪除的方式

改成刪除第k大

所以刪除還得藉助rnk qwq

//刪除第k大的點
void del(nod &o,int k)
{
    //沿途siz--,但cov不減,因為是假裝刪除qwq
    o->siz--;
    //如果當前點存在並且就是要刪的點,就刪了好了qwq
    if(o->exist&&o->rk()==k) o->exist=false;
    else if(k<=o->rk()) del(o->ch[0],k);
    else del(o->ch[1],k-o->rk());
    //否則向該去的方向遞迴,注意k的變化
}
//下面是刪k這個數,通過rnk獲取排名
void del(int k)
{ 
    del(root,rnk(k));
    //判斷是否需要重構    
    if(root->siz<root->cov*eps) rebuild(root);
}

3、查詢數x的排名

這個不解釋了,比Splay的要簡單不少了

int rnk(int k)
{
    nod o=root;
    int rank=1;
    while(o!=null)
    {
        if(o->val>=k) o=o->ch[0];
        else rank+=o->rk(),o=o->ch[1];
    }
    return rank;
}

4、查詢第k大的數

這個也差不多,平衡樹都是相通的qwq

唯一要注意的是,因為被刪除的點有些是假裝被刪除的,所以要判斷

int kth(int k)
{
    nod o=root;
    while(o!=null)
    {
        if(o->ch[0]->siz+1==k&&o->exist) return o->val;
        else if(o->ch[0]->siz>=k) o=o->ch[0];
        else k-=o->rk(),o=o->ch[1];
    }
}

5,6、前驅,後繼

直接用rnk和kth巢狀就行了

放上完整程式碼

#include<cstdio>
#include<queue>
#include<vector>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cctype>
#include<cmath>
#define _ 0
#define LL long long
#define Space putchar(' ')
#define Enter putchar('\n')
#define fuu(x,y,z) for(int x=(y);x<=(z);x++)
#define fu(x,y,z)  for(int x=(y);x<(z);x++)
#define fdd(x,y,z) for(int x=(y);x>=(z);x--)
#define fd(x,y,z)  for(int x=(y);x>(z);x--)
#define mem(x,y)   memset(x,y,sizeof(x))
#ifndef olinr
 char getc()
{
    static char buf[100001],*p1=buf,*p2=buf;
    return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,100001,stdin),p1==p2)? EOF:*p1++;
}
#else
#define getc() getchar()
#endif
template<typename T> void in(T &x)
{
    int f=1; char ch; x=0;
    while(!isdigit(ch=getc()))(ch=='-')&&(f=-f);
    while(isdigit(ch)) x=x*10+(ch^48),ch=getc();
    x*=f;
}
const double eps=0.75;
struct node
{
    int val,siz,cov;
    bool exist;
    node *ch[2];
    void upd()
    {
        siz=ch[0]->siz+ch[1]->siz+exist;
        cov=ch[0]->cov+ch[1]->cov+1;
    }
    bool nb(){ return ((ch[0]->cov>cov*eps+5)||(ch[1]->cov>cov*eps+5)); }
    int rk() { return ch[0]->siz+exist;}
};
typedef node* nod;
node st[1050500],*ts[1050500];
nod tail=st,root,null;
int top,n;
using std::vector;
nod newnode(int k)
{
    nod o=top? ts[--top]:tail++;
    o->ch[0]=o->ch[1]=null;
    o->cov=o->siz=o->exist=1;
    o->val=k;
    return o;
}
void travel(nod o,vector<nod> &v)
{
    if(o==null) return;
    travel(o->ch[0],v);
    if(o->exist) v.push_back(o);
    else ts[top++]=o;
    travel(o->ch[1],v);
}
nod divide(vector<nod> &v,int l,int r)
{
    if(l>=r) return null;
    int mid=(l+r)>>1;
    nod o=v[mid];
    o->ch[0]=divide(v,l,mid);
    o->ch[1]=divide(v,mid+1,r);
    o->upd();
    return o;
}
void rebuild(nod &o)
{
    static vector<nod> v;
    v.clear();
    travel(o,v);
    o=divide(v,0,v.size());
}
nod *ins(nod &o,int k)
{
    if(o==null)
    {
        o=newnode(k);
        return &null;
    }
    o->siz++,o->cov++;
    nod *p=ins(o->ch[k>=o->val],k);
    if(o->nb()) p=&o;
    return p;
}
void del(nod &o,int k)
{
    o->siz--;
    if(o->exist&&o->rk()==k) o->exist=false;
    else if(k<=o->rk()) del(o->ch[0],k);
    else del(o->ch[1],k-o->rk());
}
void init()
{
    null=tail++;
    null->ch[0]=null->ch[1]=null;
    null->siz=null->cov=null->val=0;
    root=null;
}
void ins(int k)
{
    nod *p=ins(root,k);
    if(*p!=null) rebuild(*p);
}
int rnk(int k)
{
    nod o=root;
    int rank=1;
    while(o!=null)
    {
        if(o->val>=k) o=o->ch[0];
        else rank+=o->rk(),o=o->ch[1];
    }
    return rank;
}
int kth(int k)
{
    nod o=root;
    while(o!=null)
    {
        if(o->ch[0]->siz+1==k&&o->exist) return o->val;
        else if(o->ch[0]->siz>=k) o=o->ch[0];
        else k-=o->rk(),o=o->ch[1];
    }
}
void del(int k)
{ 
    del(root,rnk(k));
    if(root->siz<root->cov*eps) rebuild(root);
}
int main()
{
    init();
    in(n);
    int p,x;
    while(n--)
    {
        in(p),in(x);
        if(p==1) ins(x);
        if(p==2) del(x);
        if(p==3) printf("%d\n",rnk(x));
        if(p==4) printf("%d\n",kth(x));
        if(p==5) printf("%d\n",kth(rnk(x)-1));
        if(p==6) printf("%d\n",kth(rnk(x+1)));
    }
    return ~~(0^_^0);
}