平衡树学习笔记(一):二叉搜索树、常见的平衡树
二叉搜索树
众所周知,一个区间可以有许多信息(最大值、
写一种数据结构来维护一些数,其中需要提供以下操作:
- 插入一个数
。 - 删除一个数
。 - 查询
的排名。 - 查询数据结构中排名为
的数。 - 求
的前驱。 - 求
的后继。
我们考虑一种类似二叉树的结构,它每个节点的左儿子值永远小于自己,右儿子值永远大于自己,现在向树中插入序列
- 插入
- 插入
,发现它比 小,往左子树插入。
- 插入
,发现它比 小,往左子树插入,发现它比 小,继续往左子树插入。
- 插入
,发现它比 小,往左子树插入,发现它比 大,往右子树插入。
- 插入
,发现它比 大,往右子树插入。
- 插入
,发现它比 大,往右子树插入,发现它比 小,往左子树插入。
- 插入
,发现它比 大,往右子树插入,发现它比 大,继续往右子树插入。
这样就建好了这棵二叉搜索树,考虑除插入外的操作如何实现。
插入代码:
void insert(int id, int val){
if(t[id].cnt == 0){//遇到一个新节点,直接添加
if(flag){//当前树是空树,将这个节点设为根
rt = id;
flag = false;
}
t[id].num = val;
t[id].cnt = 1;
t[id].siz = 1;
return;
}
if(t[id].num == val){//遇到一个之前添加过的节点 ,直接累加
t[id].cnt++;
} else if(val < t[id].num && !t[id].ls){//左儿子为空,且可以添加为左儿子,添加为左儿子
tot++;
t[id].ls = tot;
t[tot].fa = id;
insert(t[id].ls, val);
} else if(val > t[id].num && !t[id].rs){//右儿子为空,且可以添加为右儿子,添加为右儿子
tot++;
t[id].rs = tot;
t[tot].fa = id;
insert(t[id].rs, val);
} else if(val < t[id].num)//添加值比当前值小,往左子树插入
insert(t[id].ls, val);
else//添加值比当前值大,往右子树插入
insert(t[id].rs, val);
t[id].siz = t[id].cnt + t[t[id].ls].siz + t[t[id].rs].siz;//更新以当前节点为根的子树大小
}
删除操作
先在二叉搜索树中找到这个
-
如果
的出现次数不为 ,只用减少它的出现次数。 -
如果
的出现次数为-
如果
是叶子节点,直接删除。 -
如果
只有 个儿子,直接将 替换为它的儿子。 -
如果
有两个儿子,一般用 左子树的最大值或它右子树的最小值替换它。
-
删除代码:
int query_min(int id){//查询以id为根的子树中的最小值
if(!t[id].ls)//无法再往左儿子跳
return t[id].num;//返回当前值
return query_min(t[id].ls);//往左儿子跳
}
void del(int id, int val){
if(t[id].cnt == 0)
return;
if(val < t[id].num){//删除值比当前值小,往左子树递归
del(t[id].ls, val);
t[id].siz = t[id].cnt + t[t[id].ls].siz + t[t[id].rs].siz;
return;
}
if(val > t[id].num){//删除值比当前值大,往右子树递归
del(t[id].rs, val);
t[id].siz = t[id].cnt + t[t[id].ls].siz + t[t[id].rs].siz;
return;
}
if(t[id].cnt > 1){
t[id].cnt--;
t[id].siz = t[id].cnt + t[t[id].ls].siz + t[t[id].rs].siz;
return;
}
if(!t[id].ls && !t[id].rs){//情况1.1
t[id].cnt = 0;
t[id].siz = 0;
int fa = t[id].fa;
if(fa == 0)
flag = true;
else {
if(id == t[fa].ls)
t[fa].ls = 0;
else
t[fa].rs = 0;
t[fa].siz = t[fa].cnt + t[t[fa].ls].siz + t[t[fa].rs].siz;
}
t[id].num = INF;
t[id].fa = 0;
} else if(t[id].ls && !t[id].rs){//情况2.1
t[id].cnt = 0;
t[id].siz = 0;
int fa = t[id].fa;
if(fa != 0){
if(id == t[fa].ls)
t[fa].ls = t[id].ls;
else
t[fa].rs = t[id].ls;
t[fa].siz = t[fa].cnt + t[t[fa].ls].siz + t[t[fa].rs].siz;
} else
rt = t[id].ls;
t[t[id].ls].fa = fa;
t[id].num = INF;
t[id].ls = 0;
t[id].fa = 0;
} else if(!t[id].ls && t[id].rs){//情况2.2
t[id].cnt = 0;
t[id].siz = 0;
int fa = t[id].fa;
if(fa != 0){
if(id == t[fa].ls)
t[fa].ls = t[id].rs;
else
t[fa].rs = t[id].rs;
t[fa].siz = t[fa].cnt + t[t[fa].ls].siz + t[t[fa].rs].siz;
} else
rt = t[id].rs;
t[t[id].rs].fa = fa;
t[id].num = INF;
t[id].rs = 0;
t[id].fa = 0;
} else {//情况2.3
int tmp = query_min(t[id].rs);
t[id].num = tmp;
del(t[id].rs, tmp);
t[id].siz = t[id].cnt + t[t[id].ls].siz + t[t[id].rs].siz;
}
}
查询
每次将
查询
int rk(int id, int val){
if(id == 0)
return 0;
if(t[id].num == val)
return t[t[id].ls].siz + 1;//左子树大小加1就是排名
else if(t[id].num > val)
return rk(t[id].ls, val);
else
return rk(t[id].rs, val) + t[t[id].ls].siz + t[id].cnt;
}
查询排名为
每次将
-
如果当前根左子树大小大于等于
,则该元素在左子树中 -
如果当前根左子树大小在区间
之间,则该元素为当前根节点 -
其它情况则在右子树中。
查询排名为
int kth(int id, int k){
if(t[id].ls){
if(t[t[id].ls].siz >= k)//情况1
return kth(t[id].ls, k);
if(t[t[id].ls].siz + t[id].cnt >= k)//情况2
return t[id].num;
} else {//特判,如果当前数字添加次数比询问值大,直接返回当前值
if(k <= t[id].cnt)
return t[id].num;
}
return kth(t[id].rs, k - t[t[id].ls].siz - t[id].cnt);//情况3
}
查询
先求出
查询
首先,
完整代码:LOJ #104. 普通平衡树
LOJ上居然过了,而且跑的飞快
当你把它交到洛谷上,发现居然T了一个点,考虑有序将数添加入平衡树,你会发现这棵树退化成了一条链:
查询复杂度从
AVL
替罪羊树
Treap
完整代码:
FHQ Treap
完整代码:
#include <bits/stdc++.h>
using namespace std;
const int M = 1e6 + 9;
int cnt = 0, root = 0;
struct Node{
int ls, rs;
int key, pri;
int size;
} t[M];
void newNode(int x){
cnt++;
t[cnt].size = 1;
t[cnt].ls = t[cnt].rs = 0;
t[cnt].key = x;
t[cnt].pri = rand();
}
void Update(int u){
t[u].size = t[t[u].ls].size + t[t[u].rs].size + 1;
}
void Split(int u, int x, int &L, int &R){
if(u == 0){
L = R = 0;
return;
}
if(t[u].key <= x){
L = u;
Split(t[u].rs, x, t[u].rs, R);
} else {
R = u;
Split(t[u].ls, x, L, t[u].ls);
}
Update(u);
}
int Merge(int L, int R){
if(L == 0 || R == 0)
return L + R;
if(t[L].pri > t[R].pri){
t[L].rs = Merge(t[L].rs, R);
Update(L);
return L;
} else {
t[R].ls = Merge(L, t[R].ls);
Update(R);
return R;
}
}
int Insert(int x){
int L, R;
Split(root, x, L, R);
newNode(x);
int aa = Merge(L, cnt);
root = Merge(aa, R);
}
void Del(int x){
int L, R, p;
Split(root, x, L, R);
Split(L, x - 1, L, p);
p = Merge(t[p].ls, t[p].rs);
root = Merge(Merge(L, p), R);
}
void Rank(int x){
int L, R;
Split(root, x - 1, L, R);
printf("%d\n", t[L].size + 1);
root = Merge(L, R);
}
int kth(int u, int k){
if(k == t[t[u].ls].size + 1)
return u;
if(k <= t[t[u].ls].size)
return kth(t[u].ls, k);
if(k > t[t[u].ls].size)
return kth(t[u].rs, k - t[t[u].ls].size - 1);
}
void Precursor(int x){
int L, R;
Split(root, x - 1, L, R);
printf("%d\n", t[kth(L, t[L].size)].key);
root = Merge(L, R);
}
void Successor(int x){
int L, R;
Split(root, x, L, R);
printf("%d\n", t[kth(R, 1)].key);
root = Merge(L, R);
}
int main(){
srand(time(NULL));
int n;
scanf("%d", &n);
while(n--){
int opt, x;
scanf("%d%d", &opt, &x);
switch(opt){
case 1: Insert(x); break;
case 2: Del(x); break;
case 3: Rank(x); break;
case 4: printf("%d\n", t[kth(root, x)].key); break;
case 5: Precursor(x); break;
case 6: Successor(x); break;
}
}
return 0;
}
splay
本文来自博客园,作者:JPGOJCZX,转载请注明原文链接:https://www.cnblogs.com/JPGOJCZX/p/18422800
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」