替罪羊树学习笔记
作者:互联网
Part 0 引子
我们都知道,有一种东西叫 BST。
我们都知道,BST 在极限数据会卡爆。
我们都知道,为了让 BST 不被卡,有很多种平衡树。
但你知道有一种平衡树好写速度快吗?那就是替罪羊树。
Part 1 替罪羊树平衡的原理
替罪羊树是一种平衡树,一种平衡的 BST。
什么?你连 BST 都不会?建议学完 BST 再来看替罪羊。
我们以这题为例讲解。
先放一个最基础的数组。
struct node{
int ls,rs,siz,tsi,val,cnt;//ls rs 左右儿子,siz 子树大小,tsi 有效子树大小,val 权值,cnt 这个点的重复次数
}tree[100010];
Part 2 替罪羊树的核心原理
BST 在极端数据的时候,可以被卡成:
就很不好了。
别的平衡树都是旋转,我替罪羊不是,我替罪羊是平衡树中一只特立独行的树,是靠着拍扁重构的。
还是拿上面的那个图举例子,我们假装这是一棵大树的子树。
替罪羊树维护平衡,主要就是一个 \(\alpha\) 因子,这个因子的意思是:
这个点的大小乘上个 \(\alpha\) 是不是小于我某个左右儿子
如果成立,这个点会大喊一声:“我劝你耗子尾汁,你不对劲”拍扁重构!
拍扁重构是一个非常暴力的事情。
我们是都知道的,BST 中序遍历后是一个有序数组。
那么有了这个有序数组我们可以干嘛呢?我们每次先找到中间的值,然后左半边是它的左子树,右半边是他的右子树,然后对于左半边和右半边,我们依然按照这样处理,直到叶子节点。显然,这样划分每个层都得到了充分的利用,树高 \(\log_2 n\)。
所以……替罪羊树就是这样干一遍…
就很恐怖。
代码:
void traverse(int now)//中序遍历
{
if(tree[now].ls)traverse(tree[now].ls);
if(tree[now].cnt)rest[++S]=now;
if(tree[now].rs)traverse(tree[now].rs);
return ;
}
void updata(int now)//更新该点信息
{
tree[now].siz=tree[tree[now].ls].siz+tree[tree[now].rs].siz+tree[now].cnt;
tree[now].tsi=tree[tree[now].ls].tsi+tree[tree[now].rs].tsi+tree[now].cnt;
}
int build(int l,int r)
{
if(l==r)return 0;
int mid=(l+r)>>1;//中间的是根
tree[rest[mid]].ls=build(l ,mid);
tree[rest[mid]].rs=build(mid+1,r);
updata(rest[mid]);
return rest[mid];
}
void Start_build(int &k)
{
S=0;//S 是内存池下标
traverse(k);//遍历
k=build(1,S+1);//重构
}
Part 3 替罪羊树的经典操作
Part 3.1 插入
替罪羊树的插入和 BST 一样。
假设我们现在找到了 \(now\) 号节点,分 \(4\) 种情况。
- 刚好要插入的 \(val\) 和 \(now\) 号节点的 \(val\) 一样,就把 \(now\) 的 \(cnt+1\)。
- \(val\) 比 \(now\) 的 \(val\) 小,到 \(now\) 的左子树转悠。
- \(val\) 比 \(now\) 的 \(val\) 大,到 \(now\) 的右子树转悠。
- 这个点空着,那就给 \(val\) 八~
记得插完了以后判断重构呀。
void T_insert(int &now,int val)
{
if(!now)//4
{
now=++T_s;
if(!root)root=1;
tree[now].val=val,tree[now].ls=tree[now].rs=0;
tree[now].cnt=tree[now].siz=tree[now].tsi=1;
}
else
{
if(tree[now].val==val)tree[now].cnt++;//1
else if(tree[now].val>val)T_insert(tree[now].ls,val);//2
else T_insert(tree[now].rs,val);//3
updata(now);//updata 用于更新子树大小
if(check(now))Start_build(now);
}
}
Part 3.2 删除
删除是替罪羊树最好玩的操作。
如果我们删掉了这个节点,那这个节点的儿子们该何去何从?
所以,我们打上个标记就好了~
什么?这么简单?
没错,就这么简单/cy
这就是定义的时候有一个 \(tsi\) 了,这个代表有效节点个数。
具体实现有 \(4\) 步。
- 删除 \(now\) 的有效节点数。
- \(val\) 和 \(now\) 的 \(val\),一样,减去 \(cnt\)。
- \(val\) 小于 \(now\) 的 \(val\),就去左子树转悠。
- \(val\) 大于 \(now\) 的 \(val\),就到右子树转悠。
void T_delete(int &now,int val)
{
tree[now].tsi--;
if(tree[now].val==val)tree[now].cnt--;
else if(tree[now].val>val)T_delete(tree[now].ls,val);
else T_delete(tree[now].rs,val);
updata(now);
if(check(now))Start_build(now);
}
Part 3.3 按排名找值
这个我个人认为是替罪羊树最难的操作(大雾
老样子,四步走(为什么都是四步)
- \(now\) 的左右儿子相等,走不下去了,就返回。
- 左儿子够用,就在左儿子里面递归。
- 单靠左儿子不够用,但是加上根节点的 \(cnt\) 就够用了,说明卡在了根节点上,就返回。
- 只能靠右儿子了,这个时候,我们默认左儿子用完了,所以递归的时候就把 \(k\) 减去这个左儿子的子树。
注意:此处使用的子树是 \(tsi\),不是 \(siz\)
int T_findv(int now,int k)
{
if(tree[now].ls==tree[now].rs)return tree[now].val;
else if(tree[tree[now].ls].tsi>=k)return T_findv(tree[now].ls,k);
else if(k>tree[tree[now].ls].tsi&&tree[tree[now].ls].tsi+tree[now].cnt>=k)return tree[now].val;
else return T_findv(tree[now].rs,k-tree[tree[now].ls].tsi-tree[now].cnt);
}
Part 3.4 按值找排名
这个操作也很好玩。
分类讨论。
1.当前 \(now\) 的值和要查询的一样,就加上比 \(val\) 小的,也就是左子树大小
2.当前 \(now\) 的值比要查询的大了,就去左子树找,因为左子树小。
3.当前 \(now\) 的值比要查询的小了,首先左子树和该点本身的 \(cnt\) 一定要加上,然后还有右子树的也要找一下。
int T_findr(int now,int val)
{
if(!now)return 0;
if(tree[now].cnt&&tree[now].val==val)return tree[tree[now].ls].tsi;
else if(val<tree[now].val)return T_findr(tree[now].ls,val);
else return tree[tree[now].ls].tsi+tree[now].cnt+T_findr(tree[now].rs,val);
}
注意,这段找的实际上是排名 -1,或者说是比 \(val\) 小的
Part 3.5 前驱后继
前驱后继实在太简单了,就放到一块儿写了。
前驱就是找小的,我们上面的函数是找 \(val\) 小的,刚刚好,在按排名找值周套一个找比 \(val\) 小的。
后继就是找大的,我们要知道最小大于 \(val\) 的排名是多少,显然是 \(val+1\) 的排名。
然后按排名找值就完了。
注意,按排名找值是从 1 开始计数,而按值找排名是从 0 开始计数,一定要注意。这样的小细节请大家自行思考。
int upper(int val)
{
return T_findv(root,T_findr(root,val));
}
int lower(int val)
{
return T_findv(root,T_findr(root,val+1)+1);
}
### Part 3.6 完整代码
用 struct 封装的,码风有点丑,见谅 qwq
```cpp
#include<iostream>
#include<algorithm>
using namespace std;
double alpha=0.75;
int rest[100010];
struct Scapegoat_Tree{
int S;//内存池下标
int root;
int T_s;
struct node{
int ls,rs,siz,tsi,val,cnt;
}tree[100010];
void first()
{
S=root=0;
}
void test(int now)
{
if(tree[now].ls)test(tree[now].ls);
cout<<tree[now].val<<' ';
if(tree[now].rs)test(tree[now].rs);
/*for(int p=0;p<=5;p++)
cout<<tree[p].ls<<' '<<tree[p].rs<<' '<<tree[p].siz<<' '<<tree[p].tsi<<' '<<tree[p].val<<' '<<tree[p].cnt<<endl;*/
}
bool check(int now)
{
if(tree[now].cnt)
if(double(max(tree[tree[now].ls].siz,tree[tree[now].rs].siz))>alpha*double(tree[now].tsi))return true;
else return false;
}
void traverse(int now)
{
if(tree[now].ls)traverse(tree[now].ls);
if(tree[now].cnt)rest[++S]=now;
if(tree[now].rs)traverse(tree[now].rs);
return ;
}
void updata(int now)
{
tree[now].siz=tree[tree[now].ls].siz+tree[tree[now].rs].siz+tree[now].cnt;
tree[now].tsi=tree[tree[now].ls].tsi+tree[tree[now].rs].tsi+tree[now].cnt;
}
int build(int l,int r)
{
if(l==r)return 0;
int mid=(l+r)>>1;
tree[rest[mid]].ls=build(l ,mid);
tree[rest[mid]].rs=build(mid+1,r);
updata(rest[mid]);
return rest[mid];
}
void Start_build(int &k)
{
S=0;
traverse(k);
k=build(1,S+1);
}
void T_insert(int &now,int val)
{
if(!now)
{
now=++T_s;
if(!root)root=1;
tree[now].val=val,tree[now].ls=tree[now].rs=0;
tree[now].cnt=tree[now].siz=tree[now].tsi=1;
}
else
{
if(tree[now].val==val)tree[now].cnt++;
else if(tree[now].val>val)T_insert(tree[now].ls,val);
else T_insert(tree[now].rs,val);
updata(now);
if(check(now))Start_build(now);
}
}
void T_delete(int &now,int val)
{
tree[now].tsi--;
if(tree[now].val==val)tree[now].cnt--;
else if(tree[now].val>val)T_delete(tree[now].ls,val);
else T_delete(tree[now].rs,val);
updata(now);
if(check(now))Start_build(now);
}
int T_findv(int now,int k)
{
if(tree[now].ls==tree[now].rs)return tree[now].val;
else if(tree[tree[now].ls].tsi>=k)return T_findv(tree[now].ls,k);
else if(k>tree[tree[now].ls].tsi&&tree[tree[now].ls].tsi+tree[now].cnt>=k)return tree[now].val;
else return T_findv(tree[now].rs,k-tree[tree[now].ls].tsi-tree[now].cnt);
}
int T_findr(int now,int val)
{
if(!now)return 0;
if(tree[now].cnt&&tree[now].val==val)return tree[tree[now].ls].tsi;
else if(val<tree[now].val)return T_findr(tree[now].ls,val);
else return tree[tree[now].ls].tsi+tree[now].cnt+T_findr(tree[now].rs,val);
}
int upper(int val)
{
return T_findv(root,T_findr(root,val));
}
int lower(int val)
{
return T_findv(root,T_findr(root,val+1)+1);
}
}Tree;
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(false);
Tree.first();
int m,opt,x;
cin>>m;
while(m--)
{
cin>>opt>>x;
if(opt==1)Tree.T_insert(Tree.root,x);
else if(opt==2)Tree.T_delete(Tree.root,x);
else if(opt==3)cout<<Tree.T_findr(Tree.root,x)+1<<endl;
else if(opt==4)cout<<Tree.T_findv(Tree.root,x)<<endl;
else if(opt==5)cout<<Tree.upper(x)<<endl;
else if(opt==6)cout<<Tree.lower(x)<<endl;
}
}
标签:val,tsi,int,替罪羊,tree,笔记,学习,ls,now 来源: https://www.cnblogs.com/thirty-two/p/14315461.html