浅谈平衡树之朝鲜树

Posted lbssxz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈平衡树之朝鲜树相关的知识,希望对你有一定的参考价值。

前置知识:

BST二叉搜索树:

度娘曰:

若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。

也就是说,你把它从根节点中序遍历一边就能得到一个从小到大的数列。

大概长这样子:

技术图片

 

 对于4:左边子树节点的权值为0 1 2 3,都比4小,右边子树节点的权值为5 6 7,都比4大。

对于1:左边子树节点权值为0,比1小,右边子树节点权值为2 3,比1大(且比4小)。

对于其他节点同理。

一个不难理解的东西。

code:以luoguP3369的模板为例子

#include<cstdio>
#include<algorithm>
#define N 100010
using namespace std;

struct Node{
    int l,r;//左右节点
    int data;当前节点代表的值
    int siz,cnt;//以当前结点为根的子树的大小,以及当前结点代表的值出现的次数
}t[N];

int n,cnt,root;

void pushup(int rt){//从深到浅的更新树的大小
    int l=t[rt].l;
    int r=t[rt].r;
    t[rt].siz=t[l].siz+t[r].siz+t[rt].cnt;
}

void insert(int &rt,int x){//插入
    if(rt==0){//当前结点不存在,就创建新的节点并保存相关信息
        rt=++cnt;
        t[rt].data=x;
        t[rt].cnt=t[rt].siz=1;
        return;
    }
    if(t[rt].data==x){//正好查询到了
        t[rt].cnt++;
        t[rt].siz++;
        return;
    }
    if(t[rt].data>x){//要插的节点小于当前结点的值,根据BST性质,要往左子树找
        insert(t[rt].l,x);
        pushup(rt);
        return;
    }
    if(t[rt].data<x){//与上面反之
        insert(t[rt].r,x);
        pushup(rt);
        return;
    }
}

int delmin(int &rt){
    if(t[rt].l){
        int ret=delmin(t[rt].l);
        pushup(rt);
        return ret;
    }
    int ret=rt;
    rt=t[rt].r;
    return ret;
}

void del(int &rt,int x){//删除
    if(t[rt].data>x){
        del(t[rt].l,x);
        pushup(rt);
    }
    if(t[rt].data<x){
        del(t[rt].r,x);
        pushup(rt);
    }
    if(t[rt].data==x){
        if(t[rt].cnt>1){
            t[rt].cnt--;
            t[rt].siz--;
            return;
        }
        if(t[rt].l==0){
            rt=t[rt].r;
            return;
        }
        if(t[rt].r==0){
            rt=t[rt].l;
            return;
        }
        int tmp=delmin(t[rt].r);
        t[rt].data=t[tmp].data;
        t[rt].cnt=t[tmp].cnt;
        pushup(rt);
        return;
    }
}

int getk(int rt,int x){//这个数是第几大
    if(t[rt].data==x) return t[t[rt].l].siz+1;
    if(t[rt].data<x) return (t[rt].siz-t[t[rt].r].siz)+getk(t[rt].r,x);
    if(t[rt].data>x) return getk(t[rt].l,x);
}

int getkth(int rt,int x){//第k大是什么数
    if(t[t[rt].l].siz+1<=x&&x<=t[t[rt].l].siz+t[rt].cnt) return t[rt].data;
    if(t[t[rt].l].siz+1>x) return getkth(t[rt].l,x);
    if(x>t[t[rt].l].siz+t[rt].cnt) return getkth(t[rt].r,x-(t[t[rt].l].siz+t[rt].cnt));
}

int getpre(int rt,int x){//前驱
    int p=rt,ans;
    while(p){
        if(x<=t[p].data){
            p=t[p].l;
        }else{
            ans=p;
            p=t[p].r;
        }
    }
    return ans;
}

int getsuc(int rt,int x//后继
    int p=rt,ans;
    while(p){
        if(x>=t[p].data){
            p=t[p].r;
        }else{
            ans=p;
            p=t[p].l;
        }
    }
    return ans;
}

int main(){
    scanf("%d",&n);
    while(n--){
        int opt,x;
        scanf("%d%d",&opt,&x);
        if(opt==1){
            insert(root,x);
        }
        if(opt==2){
            del(root,x);
        }
        if(opt==3){
            printf("%d
",getk(root,x));
        }
        if(opt==4){
            printf("%d
",getkth(root,x));
        }
        if(opt==5){
            printf("%d
",t[getpre(root,x)].data);
        }
        if(opt==6){
            printf("%d
",t[getsuc(root,x)].data);
        }
    }
    return 0;
}

 


朝鲜树:

鬼才知道为什么叫做朝鲜树,但是和朝鲜没有半毛钱关系

在BST中,我们或许会遇到以下情况:

技术图片

 

 没错它和你一样偏科了。

在上面的树中,毒瘤出题人如果让你查询最大的数,也就是右下角节点的树,你会发现它接近于O(n)了。而理想的情况是每一次查询复杂度为O(logn)。

而无能的BST无法对其进行变通,于是大神们发明了平衡树算法,搞一搞BST变种,使得树的深度均摊一下,以保证复杂度均摊。

朝鲜树就是平衡树的一种,

原理:

我们定义一个MAXdeep,表示整棵树最深的深度限制。如果当这棵树的深度超过了MAXdeep,就进行一次均摊重构,使得其在满足BST性质下,深度尽量保持最小。

maxdeep根据数据可获取。

建议跟着我的顺序,从main函数开始看

code:

#include<cstdio>
#include<algorithm>
#define N 100010
using namespace std;

struct Node{
    int l,r;
    int data;
    int siz,cnt;
    int dep,mxd;//不一样的地方。当前结点深度,最深深度限制
}t[N];

int n,cnt,root;//root表示当前整棵朝鲜树的根节点编号

void pushup(int rt){
    int l=t[rt].l;
    int r=t[rt].r;
    t[rt].siz=t[l].siz+t[r].siz+t[rt].cnt;
    t[rt].mxd=max(t[l].mxd,t[r].mxd);//加入了maxdeep值的更改操作
}

bool cmp(const Node& a,const Node& b){//重构时需要根据大小排序,所以要用到sort
    return a.data<b.data;
}

int build(int l,int r,int dep){//圈3,重构函数,过程类似于线段树,很好理解。
    if(l>r) return 0;
    int mid=(l+r)>>1;
    t[mid].l=build(l,mid-1,dep+1);
    t[mid].r=build(mid+1,r,dep+1);
    t[mid].dep=dep;
    pushup(mid);
    return mid;
}

void rebuild(){//圈2,首先把所有节点进行从小到大排序,保证满足BST性质,然后建树,获取新树的根节点root。往上看到build:
    sort(t+1,t+1+cnt,cmp);
    root=build(1,cnt,1);
}

void insert(int &rt,int x,int dep){
    if(rt==0){
        rt=++cnt;
        t[rt].data=x;
        t[rt].cnt=t[rt].siz=1;
        t[rt].dep=t[rt].mxd=dep;//更新maxdeep
        return;
    }
    if(t[rt].data==x){
        t[rt].cnt++;
        t[rt].siz++;
        return;
    }
    if(t[rt].data>x){
        insert(t[rt].l,x,dep+1);
        pushup(rt);
        return;
    }
    if(t[rt].data<x){
        insert(t[rt].r,x,dep+1);
        pushup(rt);
        return;
    }
}

int delmin(int &rt){
    if(t[rt].l){
        int ret=delmin(t[rt].l);
        pushup(rt);
        return ret;
    }
    int ret=rt;
    rt=t[rt].r;
    return ret;
}

void del(int &rt,int x){
    if(t[rt].data>x){
        del(t[rt].l,x);
        pushup(rt);
    }
    if(t[rt].data<x){
        del(t[rt].r,x);
        pushup(rt);
    }
    if(t[rt].data==x){
        if(t[rt].cnt>1){
            t[rt].cnt--;
            t[rt].siz--;
            return;
        }
        if(t[rt].l==0){
            rt=t[rt].r;
            return;
        }
        if(t[rt].r==0){
            rt=t[rt].l;
            return;
        }
        int tmp=delmin(t[rt].r);
        t[rt].data=t[tmp].data;
        t[rt].cnt=t[tmp].cnt;
        pushup(rt);
        return;
    }
}

int getk(int rt,int x){
    if(t[rt].data==x) return t[t[rt].l].siz+1;
    if(t[rt].data<x) return (t[rt].siz-t[t[rt].r].siz)+getk(t[rt].r,x);
    if(t[rt].data>x) return getk(t[rt].l,x);
}

int getkth(int rt,int x){
    if(t[t[rt].l].siz+1<=x&&x<=t[t[rt].l].siz+t[rt].cnt) return t[rt].data;
    if(t[t[rt].l].siz+1>x) return getkth(t[rt].l,x);
    if(x>t[t[rt].l].siz+t[rt].cnt) return getkth(t[rt].r,x-(t[t[rt].l].siz+t[rt].cnt));
}

int getpre(int rt,int x){
    int p=rt,ans;
    while(p){
        if(x<=t[p].data){
            p=t[p].l;
        }else{
            ans=p;
            p=t[p].r;
        }
    }
    return ans;
}

int getsuc(int rt,int x){
    int p=rt,ans;
    while(p){
        if(x>=t[p].data){
            p=t[p].r;
        }else{
            ans=p;
            p=t[p].l;
        }
    }
    return ans;
}

int main(){
    scanf("%d",&n);
    while(n--){
        int opt,x;
        scanf("%d%d",&opt,&x);
        if(opt==1){
            insert(root,x,1);
        }
        if(opt==2){
            del(root,x);
        }
        if(opt==3){
            printf("%d ",getk(root,x));
        }
        if(opt==4){
            printf("%d ",getkth(root,x));
        }
        if(opt==5){
            printf("%d ",t[getpre(root,x)].data);
        }
        if(opt==6){
            printf("%d ",t[getsuc(root,x)].data);
        }
        if(t[root].mxd>100) rebuild();//圈1,如果深度大于100,我们就进行重构操作。向上跳到rebuild函数继续看。
    }
    return 0;
}

询问》排序》重构,这就是朝鲜树。

完结。

 

以上是关于浅谈平衡树之朝鲜树的主要内容,如果未能解决你的问题,请参考以下文章

平衡树之非旋Treap

浅谈算法和数据结构:平衡查找树之红黑树

转 浅谈算法和数据结构: 十 平衡查找树之B树

浅谈算法和数据结构: 九 平衡查找树之红黑树

平衡树之Splay

二叉平衡树之删除节点