平衡树学习笔记
替罪羊树
-
平衡因子。 ( ) -
替罪羊树原理:当树不平衡时,中序遍历收集树上的所有节点,然后用二分的方式重构整棵树,使其变成标准的平衡树。查询代价由树高决定,重构代价由重构节点个数和平衡因子决定。
-
节点信息:
- val:键值,代表节点储存的信息。
- count:平衡树最好不要有键值相同的节点, 插入重复的值只需要把对应节点的count 值修改就可以了。
- left,right:左右孩子指针。
- diff: 子树中含有的节点数的多少。(即不同的数的个数)
- size:记录树上总共收集了多少个变量。
-
因子越接近 1,重构次数越少,查询复杂度上升, 越接近 0.5 ,树高越小,重构次数越多,查询时间复杂度下降。
class TZYTree{
public:
#define ls(p) t[p].l
#define rs(p) t[p].r
struct node{
int l , r; // 左右孩子指针, 如果没有孩子就为0
int val; // 代表当前节点的值
int diff; // 代表子树中有多少个节点
int count; // 代表当前节点有多少个数
int size; // 代表子树中有多少个数
node() : l(0) , r(0), diff(0),count(0),size(0),val(0)
{}
};
const double alpha = 0.7; // 平衡因子
int cnt = 0; // 代表有序表当前已使用的节点数
int top = 0; // 代表最上方不平衡的节点, 如果 top = 0 , 则说明不存在不平衡的节点
int Head = 0; // 记录替罪羊树的根节点的下标, 如果表中无元素则下标为 0
int father = 0; // 记录最上方不平衡的节点的父节点, 重构之后可能需要修改指针, 如果是整棵树 , 那么 father = 0
int side = 0; // 记录不最上方的不平衡节点是它的父节点的左孩子还是右孩子 , 1为左孩子 , 0 为右孩子
int pos = 0; // 记录 order 表中最后一个数据的下标 , 即 1 - pos
static const int inf = INT_MAX;
std::vector<node> t;
std::vector<int> order;
TZYTree (int n) {
// n 代表有序表最大数量
assign(n);
}
void assign(int n){
t.assign( n + 2,node());
order.assign(n+2,0);
}
// 新增一个节点
int add(int x) {
t[++cnt].val = x;
t[cnt].size = t[cnt].diff =t[cnt].count = 1;
return cnt;
}
void up( int p){
t[p].size = t[ls(p)].size + t[rs(p)].size + t[p].count;
t[p].diff = t[ls(p)].diff + t[rs(p)].diff + (t[p].count > 0);
}
// 收集为中序遍历
void inorder(int p) {
if (p !=0)
{
inorder(ls(p));
if(t[p].count >0 ){
order[++pos] = p; // 注意这里只是中序遍历把节点下标上传
}
inorder(rs(p));
}
}
// l 代表order 中的序号 , 中序遍历重构
int build(int l , int r) {
if(l > r) return 0;
int mid = (l + r)/2;
int head = order[mid];
ls(head) = build(l,mid-1);
rs(head) = build(mid+1,r);
up(head);
return head;
}
void rebuild() {
if(top != 0) {
// 即存在不平衡现象
pos = 0;
inorder(top);
if (pos > 0) {
if( father == 0) {
Head = build(1,pos); // 头节点重连
} else if(side == 1) {
ls(father) = build(1,pos);
} else {
rs(father) = build(1,pos);
}
}
}
}
bool balance(int p) {
// 如果子树中的节点数大于 整颗树的节点数乘以 alpha 倍, 则重构
return alpha * t[p].diff >= std::max(t[ls(p)].diff , t[rs(p)].diff);
}
void insert(int number) {
top = father = side = 0;
insert(Head,0,0,number);
rebuild();
}
void insert(int p,int f,int s,int num) {
if(p == 0) {
if(f == 0) {
Head = add(num);
} else if(s == 1) {
ls(f) = add(num);
} else {
rs(f) = add(num);
}
} else {
// assert(p >t.size());
if( t[p].val == num) {
t[p].count++;
} else if(t[p].val > num) {
insert(ls(p),p,1,num);
} else {
insert(rs(p),p,2,num);
}
up(p);
if(!balance(p)) {
top = p;
father = f;
side = s;
}
}
}
void remove(int p, int f, int s,int num) {
if(t[p].val == num) {
t[p].count--;
} else if(t[p].val > num) {
remove(ls(p),p,1,num);
} else {
remove(rs(p),p,2,num);
}
up(p);
if(!balance(p)) {
father = f , side = s , top = p;
}
}
void remove(int num) {
if(rank(num) != rank(num+1)) {
top = father = side = 0;
remove(Head,0,0,num);
}
}
int rank(int num) {
return small(Head,num) + 1;
}
int small(int p , int num) {
if(p == 0) {
return 0;
}
if(t[p].val >= num) {
return small(ls(p),num);
} else {
return t[ls(p)].size + small(rs(p),num) + t[p].count;
}
}
int index(int x) {
return index(Head,x);
}
int index(int p, int x) {
if(t[ls(p)].size >= x) {
return index(ls(p),x);
} else if(t[ls(p)].size + t[p].count >= x) {
return p;
} else {
return index(rs(p),x - t[ls(p)].size - t[p].count);
}
}
// 查找小于某个数的最大值
int pre(int x) {
int kth = rank(x);
if(kth == 1) {
return -inf;
} else {
return index(kth-1);
}
}
// 查找大于某个数的最小值
int post(int x) {
int kth = rank(x + 1);
if(kth == t[Head].size + 1) { // 也就是没有大于某个数的值就放回 inf
return inf;
} else {
return index(kth);
}
}
void clear(){
Head = cnt = 0;
t.clear();
order.clear();
}
#undef ls
#undef rs
};
任何时候都有比放弃更好的选择。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)