文章目录
一.什么是平衡树
就这样给你说,平衡树就是一个节点的左儿子严格小于它,右儿子严格大于它的一棵二叉树,整个树的叶子结点的深度几乎一致。
二.分步讲解
1.定义变量
key[i]:排序的关键字
cnt[i]:每个节点的数的个数
f[i]:父节点
ch[i][0]:左儿子;ch[i][1]:右儿子
siz[i]:以i为根的子树大小
sz:节点总数
root:根节点的编号
4.预备函数
void clear (int x){//清零
ch[x][0] = ch[x][1] = key[x] = cnt[x] = f[x] = siz[x] = 0;
}
void update (int x){//更新
siz[x] = siz[ch[x][0]] + siz[ch[x][1]] + cnt[x];
}
3.旋转操作
给一张图就行了
看懂了吗?
void rotate (int x){
int fa = f[x], gfa = f[fa], d = (x == ch[fa][1]);//d是找x是fa的哪一个儿子节点
ch[fa][d] = ch[x][1 ^ d];
f[ch[fa][d]] = fa;//***
ch[x][1 ^ d] = fa;
f[fa] = x;
f[x] = gfa;
if (gfa){
int d1 = (fa == ch[gfa][1]);
ch[gfa][d1] = x;
}
update (x), update (fa), update (gfa);//注意更新
}
3.splay
splay的作用就是把当前节点旋转到根,那么根就更新了,并且树是平衡的。
void splay (int x){
for (int fa; fa = f[x]; rotate(x))
if (f[fa])
rotate ((ch[f[fa]][1] == fa) == (ch[fa][1] == x) ? fa : x);
//这里要注意,如果当前节点,父亲节点和爷爷节点在一条线上,就要先旋转父亲节点,再旋转当前节点,注意不能失衡
root = x;
}
4.插入
void insert (int x){
if (! root){//第一种情况,是空树
sz ++;
ch[sz][0] = ch[sz][1] = f[sz] = 0;
cnt[sz] = siz[sz] = 1;
key[sz] = x;
root = sz;
return ;
}
int now = root, fa = 0;
while (1){//第二种情况,要插到叶节点去
if (key[now] == x){
cnt[now] ++;
update (now);
update (fa);
splay (now);//注意要旋转到根部
return ;
}
fa = now;
now = ch[now][x > key[now]];
if (! now){
sz ++;
ch[sz][0] = ch[sz][1] = 0;
f[sz] = fa;
key[sz] = x;
cnt[sz] = siz[sz] = 1;
ch[fa][x > key[fa]] = sz;
update (fa);
splay (sz);
return ;
}
}
}
5.查询x的排名
int find (int x){
int ans = 0, now = root;
while (1){
if (key[now] > x){//如果当前节点关键字比x大,则查找左儿子
now = ch[now][0];
}
else{
ans += ch[now][0] ? siz[ch[now][0]] : 0;
if (x == key[now]){//找到了
splay (now);
return ans + 1;
}
ans += cnt[now];
now = ch[now][1];//以此类推
}
}
}
6.查询排名x的数
int findx (int x){//很简单,容易理解
int now = root;
while (1){
if (ch[now][0] && siz[ch[now][0]] >= x)
now = ch[now][0];
else{
x -= ch[now][0] ? siz[ch[now][0]] : 0;
if (x <= cnt[now])
return key[now];
x -= cnt[now];
now = ch[now][1];
}
}
}
7.前驱(比x小的最大的)
int pre (){
int now = ch[root][0];
while (ch[now][1])
now = ch[now][1];
return now;
}
8.后继(比x大的最小的)
int nex (){
int now = ch[root][1];
while (ch[now][0])
now = ch[now][0];
return now;
}
9.删除
void del (int x){//一共有四种情况,详见下
find (x);//先把当前节点旋到根
if (cnt[root] > 1){//如果当前节点的个数不止一个,就直接减
cnt[root] --;
return ;
}
if (! ch[root][0] && ! ch[root][1]){//如果当前节点没有子节点,就直接删
clear (root);
root = 0;
return ;
}
//如果只有一个子节点,就让其子节点成为根
if (! ch[root][0]){
int oldroot = root;
root = ch[root][1];
f[root] = 0;
clear (oldroot);
return ;
}
else if (! ch[root][1]){
int oldroot = root;
root = ch[root][0];
f[root] = 0;
clear (oldroot);
return ;
}
//如果有两个子节点,就把左面最大的后辈旋到根,然后更新节点信息就行了
int oldroot = root, furoot = pre ();
splay (furoot);
ch[root][1] = ch[oldroot][1];
f[ch[oldroot][1]] = root;
update (root);
clear (oldroot);
}
三.模板
此题是模板题:【模板】普通平衡树
Code:
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define M 100005
int n, ch[M][2], key[M], cnt[M], f[M], siz[M], root, sz;
void clear (int x){
ch[x][0] = ch[x][1] = key[x] = cnt[x] = f[x] = siz[x] = 0;
}
void update (int x){
siz[x] = siz[ch[x][0]] + siz[ch[x][1]] + cnt[x];
}
void rotate (int x){
int fa = f[x], gfa = f[fa], d = (x == ch[fa][1]);
ch[fa][d] = ch[x][1 ^ d];
f[ch[fa][d]] = fa;//***
ch[x][1 ^ d] = fa;
f[fa] = x;
f[x] = gfa;
if (gfa){
int d1 = (fa == ch[gfa][1]);
ch[gfa][d1] = x;
}
update (x), update (fa), update (gfa);
}
void splay (int x){
for (int fa; fa = f[x]; rotate(x))
if (f[fa])
rotate ((ch[f[fa]][1] == fa) == (ch[fa][1] == x) ? fa : x);
root = x;
}
void insert (int x){
if (! root){
sz ++;
ch[sz][0] = ch[sz][1] = f[sz] = 0;
cnt[sz] = siz[sz] = 1;
key[sz] = x;
root = sz;
return ;
}
int now = root, fa = 0;
while (1){
if (key[now] == x){
cnt[now] ++;
update (now);
update (fa);
splay (now);
return ;
}
fa = now;
now = ch[now][x > key[now]];
if (! now){
sz ++;
ch[sz][0] = ch[sz][1] = 0;
f[sz] = fa;
key[sz] = x;
cnt[sz] = siz[sz] = 1;
ch[fa][x > key[fa]] = sz;
update (fa);
splay (sz);
return ;
}
}
}
int find (int x){
int ans = 0, now = root;
while (1){
if (key[now] > x){
now = ch[now][0];
}
else{
ans += ch[now][0] ? siz[ch[now][0]] : 0;
if (x == key[now]){
splay (now);
return ans + 1;
}
ans += cnt[now];
now = ch[now][1];
}
}
}
int findx (int x){
int now = root;
while (1){
if (ch[now][0] && siz[ch[now][0]] >= x)
now = ch[now][0];
else{
x -= ch[now][0] ? siz[ch[now][0]] : 0;
if (x <= cnt[now])
return key[now];
x -= cnt[now];
now = ch[now][1];
}
}
}
int pre (){
int now = ch[root][0];
while (ch[now][1])
now = ch[now][1];
return now;
}
int nex (){
int now = ch[root][1];
while (ch[now][0])
now = ch[now][0];
return now;
}
void del (int x){
find (x);
if (cnt[root] > 1){//***
cnt[root] --;
return ;
}
if (! ch[root][0] && ! ch[root][1]){
clear (root);
root = 0;
return ;
}
if (! ch[root][0]){
int oldroot = root;
root = ch[root][1];
f[root] = 0;
clear (oldroot);
return ;
}
else if (! ch[root][1]){
int oldroot = root;
root = ch[root][0];
f[root] = 0;
clear (oldroot);
return ;
}
int oldroot = root, furoot = pre ();
splay (furoot);
ch[root][1] = ch[oldroot][1];
f[ch[oldroot][1]] = root;
update (root);
clear (oldroot);
}
int main (){
scanf ("%d", &n);
while (n --){
int opt, x;
scanf ("%d %d", &opt, &x);
if (opt == 1)
insert (x);
if (opt == 2)
del (x);
if (opt == 3)
printf ("%d\n", find (x));
if (opt == 4)
printf ("%d\n", findx (x));
if (opt == 5){
insert (x);
printf ("%d\n", key[pre ()]);
del (x);
}
if (opt == 6){
insert (x);
printf ("%d\n", key[nex ()]);
del (x);
}
}
return 0;
}