浅谈平衡树之朝鲜树
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;
}
询问》排序》重构,这就是朝鲜树。
完结。
以上是关于浅谈平衡树之朝鲜树的主要内容,如果未能解决你的问题,请参考以下文章