数据结构与算法:有序表(三):替罪羊树

前言

这玩意儿也太抽象了,均摊的艺术。

一、原理

首先,替罪羊树也是一个二叉搜索树,所以每次插入节点就是按照二叉搜索树的方法插入。

替罪羊树的原理非常简单,就是设置一个平衡因子 alpha,每次检查以当前节点为根的子树是否平衡。方法就是,令当前节点为 h,子树大小为 n,一旦 h 的左子树或右子树的大小大于 alpha*n,此时就认为该子树不平衡。

调整的方法也很简单粗暴,就是先暴力中序遍历整棵树,此时因为是二叉搜索树,所以收集上来的必然是个有序数组。之后直接按照二分的方法暴力重构这棵树,即每次以二分的中点为头,然后去左侧和右侧重构子树。此时也可以想到,当发生不平衡时,因为反正要重构,所以肯定是选择最上方不平衡的位置,重构一次即可。

可以发现,其复杂度完全取决于这个平衡因子。当平衡因子取到小于等于 0.5 时,此时即使是非常平衡的二叉搜索树,由于左右子树都是整体的一半,也会触发重构,那么每插入一个节点就需要重构了。而当平衡因子取到大于等于 1 时,此时不管左子树还是右子树,其大小都不可能比整棵树还大,那么就永远都不会重构了。所以,为了保证重构次数和树高取到一个相对平衡的点,考虑选择平衡因子 0.7。

二、题目------普通平衡树

这也太像线段树了......

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

template<typename T>
struct ScapeGoat
{
private:

    double ALPHA=0.7;
    vector<T>key;
    vector<int>count;
    vector<int>left;
    vector<int>right;
    vector<int>size;
    vector<int>node;
    int head=0;
    int cnt;

    vector<int>tmp;
    int ci;

    int top;    //highest unbalanced node
    int father; //father of top
    int side;   //top in which side of father

    int init(T num){
        key[++cnt]=num;
        left[cnt]=right[cnt]=0;
        count[cnt]=size[cnt]=node[cnt]=1;
        return cnt;
    }

    void up(int i){
        size[i]=size[left[i]]+size[right[i]]+count[i];
        node[i]=node[left[i]]+node[right[i]]+(count[i]>0);
    }

    void inorder(int i){
        if(i!=0){
            inorder(left[i]);
            if(count[i]>0){
                tmp[++ci]=i;
            }
            inorder(right[i]);
        }
    }

    int build(int l,int r){
        if(l>r){
            return 0;
        }
        int m=l+r>>1;
        int h=tmp[m];
        left[h]=build(l,m-1);
        right[h]=build(m+1,r);
        up(h);
        return h;
    }

    void rebuild(){
        if(top!=0){ //need rebuild
            ci=0;
            inorder(top);
            if(ci>0){
                if(father==0){  //top is head
                    head=build(1,ci);
                }else if(side==1){  //top is on the left of father
                    left[father]=build(1,ci);                    
                }else{
                    right[father]=build(1,ci);
                }
            }
        }
    }

    bool balance(int i){
        return ALPHA*node[i]>=max(node[left[i]],node[right[i]]);
    }

    void insert(int i,int f,int s,T num){
        if(i==0){
            if(f==0){
                head=init(num);
            }else if(s==1){
                left[f]=init(num);
            }else{
                right[f]=init(num);
            }
        }else{
            if(key[i]==num){
                count[i]++;
            }else if(key[i]>num){
                insert(left[i],i,1,num);
            }else{
                insert(right[i],i,2,num);
            }
            up(i);
            if(!balance(i)){
                top=i,father=f,side=s;
            }
        }
    }

    void erase(int i,int f,int s,T num){
        if(key[i]==num){
            count[i]--;
        }else if(key[i]>num){
            erase(left[i],i,1,num);
        }else{
            erase(right[i],i,2,num);
        }
        up(i);
        if(!balance(i)){
            top=i,father=f,side=s;
        }
    }

    int small(int i,T num){
        if(i==0){
            return 0;
        }
        if(key[i]>=num){
            return small(left[i],num);
        }
        return size[left[i]]+count[i]+small(right[i],num);
    }

    int index(int i,int x){
        if(size[left[i]]>=x){
            return index(left[i],x);
        }else if(size[left[i]]+count[i]<x){
            return index(right[i],x-size[left[i]]-count[i]);
        }
        return key[i];
    }

public:
    ScapeGoat(int n):key(n),count(n),left(n),right(n),size(n),node(n),tmp(n){
        head=cnt=0;
    }

    void insert(T num){
        top=father=side=0;
        insert(head,0,0,num);
        rebuild();
    }

    void erase(T num){
        if(rank(num)!=rank(num+1)){
            top=father=side=0;
            erase(head,0,0,num);
            rebuild();
        }
    }

    int rank(T num){
        return small(head,num)+1;
    }

    int index(int x){
        return index(head,x);
    }

    int pre(T num){
        int kth=rank(num);
        if(kth==1){
            return -INF;
        }
        return index(kth-1);
    }

    int post(T num){
        int kth=rank(num+1);
        if(kth==size[head]+1){
            return INF;
        }
        return index(kth);
    }
};

void solve()
{
    int q;
    cin>>q;

    ScapeGoat<int>tree(q+1);

    int op,x;
    while(q--)
    {
        cin>>op;
        if(op==1)
        {
            cin>>x;

            tree.insert(x);
        }
        else if(op==2)
        {
            cin>>x;

            tree.erase(x);
        }
        else if(op==3)
        {
            cin>>x;

            cout<<tree.rank(x)<<endl;
        }
        else if(op==4)
        {
            cin>>x;

            cout<<tree.index(x)<<endl;
        }
        else if(op==5)
        {
            cin>>x;

            cout<<tree.pre(x)<<endl;
        }
        else if(op==6)
        {
            cin>>x;

            cout<<tree.post(x)<<endl;
        }
    }
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    //cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

在替罪羊树中,当一个节点的词频降到 0 了,此时不立即删除这个节点,因为可能导致重构的次数增多,所以在统计的时候需要判断词频信息。

由于每次会重构树,所以需要亿点分讨来判断修改整个头节点和子树的情况。除此之外,此时可以考虑使用 rank 和 index 方法来方便求解 pre 和 post。

总结

马上重量级就要来了()

END