今天来种一种个人最喜欢的SBTree
平衡树总结Ⅱ:SBT
概述
SBT,Size Balanced Tree,顾名思义是尽量维护子树大小(结点个数)平衡的树。
也就是当每次插入新结点后,如果发现左右子树失衡(某个结点的size比它兄弟的孩子的size都要小)就得进行旋转操作,来使子树的size始终保持在一个较为平衡的状态。
自平衡操作
① SBT的自平衡维护比较简单,只在插入时进行自平衡维护,且只有两种最基本的旋转操作:简单左旋和简单右旋:
- 左旋操作:(点击可以看大图哦)
- 右旋操作:(点击可以看大图哦)
一个值得注意的地方是,在设计旋转函数的时候,参数 i 应该是引用类型(可以发现在第四步修改了这个引用)。这样我们在第四步对 i 进行的赋值才会真的修改 i 的值(即父节点指针的值)
② 那么啥时候需要旋转呢?什么时候叫做 “左右子树size失衡” 了呢?
我们定义某个结点是平衡的当且仅当他的size不小于其兄弟结点的任一子节点的size:(如图。为了表示方便,每个结点或子树都用一个大写字母起了名字,并将其真实下标标注在正下方)
SBT在每次插入结点后会检查当前结点一直到根结点这一条链上的各个结点是否满足平衡条件,如果不满足就进行旋转。
③ 具体调整逻辑:
-
1.YD失衡(D太重,要左旋X (即i号) )
-
2.YC失衡(C太重,要先右旋Z (即t[i].ri号) ,再左旋X (即i号) )
-
3.ZA失衡(对称于YD失衡(图就不画啦):A太重,要右旋X (即i号) )
-
4.ZB失衡(对称于YC失衡(图就不画啦):B太重,要先左旋Y (即t[i].li号),再右旋X (即i号) )
是不是很简单呢 当然,最后为了写代码方便,我们还可以把调整平衡的函数加一个 bool 型参数,告知函数我刚刚是改了左子树还是右子树,如果改了右子树就只用判断是否发生了YD失衡或YC失衡,如果改了左子树就只用判断是否发生了ZA失衡或ZB失衡。并且可以在函数的最后暴力地检查当前结点、当前结点左孩子、当前结点右孩子是否需要再递归调整(稍微降低一点点运行效率,但大幅减少出 bug 概率)
④ 关键函数代码:(基本上把上面的分析翻译一下就行)
struct SBN // SBT结点
{
xint li, ri, sz; // 左孩子指针(下标)、右孩子指针(下标)、该子树的size
vint v; // 点权
} t[MN];
inline void l_rotate(xint &i) // 左旋
{
const xint ri = t[i].ri; // 记住ri
t[i].ri = t[ri].li, t[ri].li = i, t[ri].sz = t[i].sz; // 修改t[i]的右指针、t[ri]的左指针,同时修改t[ri]的size
t[i].sz = t[t[i].li].sz + t[t[i].ri].sz + 1; // 修改t[i]的size
i = ri; // 修改i的父节点的指针
/* 改引用i是因为旋转后i的父节点就不应该指向i了,而应该指向i的右孩子
所以要把i父节点指向i的指针改成指向ri的指针,所以需要传引用改i(调用时传的正是i父节点指向i的指针的引用)*/
}
inline void r_rotate(xint &i) // 右旋
{
const xint li = t[i].li; // 记住li
t[i].li = t[li].ri, t[li].ri = i, t[li].sz = t[i].sz; // 修改t[i]的左指针、t[li]的右指针,同时修改t[li]的size
t[i].sz = t[t[i].li].sz + t[t[i].ri].sz + 1; // 修改t[i]的size
i = li; // 修改i的父节点的指针
}
void maintain(xint &i, const bool rr) // 维护平衡
{
if (rr) // 如果刚才改的是右子树
{
if (t[t[t[i].ri].ri].sz > t[t[i].li].sz) // YD失衡,D太重
l_rotate(i); // 要左旋X(即i号)
else if (t[t[t[i].ri].li].sz > t[t[i].li].sz) // YC失衡,C太重
r_rotate(t[i].ri), l_rotate(i); // 要先右旋Z(即t[i].ri号);然后再左旋X(即i号)
else // 没有失衡
return;
}
else // 刚才改的是左子树
{
if (t[t[t[i].li].li].sz > t[t[i].ri].sz) // ZA失衡,A太重
r_rotate(i); // 要右旋X(即i号)
else if (t[t[t[i].li].ri].sz > t[t[i].ri].sz) // ZB失衡,B太重
l_rotate(t[i].li), r_rotate(i); // 要先左旋Y(即t[i].li号);然后再右旋X(即i号)
else // 没有失衡
return;
}
// 懒得具体分析了,直接暴力递归所有可能需要维护平衡的情况(最多就造成几次额外的if判断,不用担心会产生无用的旋转操作)
maintain(t[i].li, false), maintain(t[i].ri, true), maintain(i, false), maintain(i, true);
}
例题 洛谷P3369 【模板】普通平衡树
分析:没什么好说的…把SBT最基本的功能实现即可
代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define LL long long
#define _BS 1048576
char _bf[_BS];
char *__p1=_bf,*__p2=_bf;
#define _IO char *_p1=__p1,*_p2=__p2;
#define _OI __p1=_p1,__p2=_p2;
#ifdef _KEVIN
#define GC getchar()
#else
#define GC (_p1==_p2&&(_p2=(_p1=_bf)+fread(_bf,1,_BS,stdin),_p1==_p2)?EOF:*_p1++)
#endif
#define PC putchar
#define sc(x) {register char _c=GC,_v=1;for(x=0;_c<48||_c>57;_c=GC)if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=GC);if(_v==-1)x=-x;}
template<class T>
void PRT(const T _){if(_<0){PC(45),PRT(-_);return;}if(_>=10)PRT(_/10);PC(_%10+48);}
constexpr int MN(1e5+7);
template <typename vint, typename xint = int>
class SBT
{
private:
xint root, tot;
struct SBN
{
xint li, ri, sz;
vint v;
} t[MN];
inline void l_r(xint &i) // 左旋
{
const xint ri = t[i].ri;
t[i].ri = t[ri].li, t[ri].li = i, t[ri].sz = t[i].sz;
t[i].sz = t[t[i].li].sz + t[t[i].ri].sz + 1;
i = ri;
}
inline void r_r(xint &i) // 右旋
{
const xint li = t[i].li;
t[i].li = t[li].ri, t[li].ri = i, t[li].sz = t[i].sz;
t[i].sz = t[t[i].li].sz + t[t[i].ri].sz + 1;
i = li;
}
void mt(xint &i, const bool rr) // 维护平衡
{
if (rr) // 如果刚才改的是右子树
{
if (t[t[t[i].ri].ri].sz > t[t[i].li].sz) // YD失衡
l_r(i);
else if (t[t[t[i].ri].li].sz > t[t[i].li].sz) // YC失衡
r_r(t[i].ri), l_r(i);
else // 没有失衡
return;
}
else // 刚才改的是左子树
{
if (t[t[t[i].li].li].sz > t[t[i].ri].sz) // ZA失衡
r_r(i);
else if (t[t[t[i].li].ri].sz > t[t[i].ri].sz) // ZB失衡
l_r(t[i].li), r_r(i);
else // 没有失衡
return;
}
mt(t[i].li, 0), mt(t[i].ri, 1), mt(i, 0), mt(i, 1);
}
void _insert(xint &i, const vint v)
{
if (i)
{
++t[i].sz;
if (v < t[i].v)
_insert(t[i].li, v), mt(i, 0);
else
_insert(t[i].ri, v), mt(i, 1);
}
else
{
i = ++tot;
t[i].v = v;
t[i].sz = 1;
}
}
vint _erase(xint &i, const vint v)
{
--t[i].sz;
if (v==t[i].v || (!t[i].li && v<t[i].v) || (!t[i].ri && v>t[i].v))
{
vint tmp = t[i].v;
if (!t[i].li || !t[i].ri)
i = t[i].li + t[i].ri;
else
t[i].v = _erase(t[i].li, t[i].v+1);
return tmp;
}
if (v<t[i].v)
return _erase(t[i].li, v);
else
return _erase(t[i].ri, v);
}
xint _lower_rk(xint i, const vint v) const
{
xint rk = 1;
while (i)
{
if (t[i].v >= v)
i = t[i].li;
else
rk += t[t[i].li].sz + 1, i = t[i].ri; // 先+=再改i
}
return rk;
}
xint _upper_rk(xint i, const vint v) const
{
xint rk = 0;
while (i)
{
if (t[i].v > v)
i = t[i].li;
else
rk += t[t[i].li].sz + 1, i = t[i].ri; // 先+=再改i
}
return rk;
}
vint _nth(xint i, xint k) const
{
xint lz;
while (i)
{
lz = t[t[i].li].sz;
if (k == lz+1)
break;
if (k <= lz)
i = t[i].li;
else
i = t[i].ri, k -= lz + 1;
}
return t[i].v;
}
vint _prev(const xint i, const vint v) const
{
if (!i)
return v;
vint tp;
if (v <= t[i].v)
tp = _prev(t[i].li, v);
else
{
tp = _prev(t[i].ri, v);
if (tp == v)
tp = t[i].v;
}
return tp;
}
vint _next(const xint i, const vint v) const
{
if (!i)
return v;
vint tp;
if (v >= t[i].v)
tp = _next(t[i].ri, v);
else
{
tp = _next(t[i].li, v);
if (tp == v)
tp = t[i].v;
}
return tp;
}
// interfaces
public:
inline void init(void)
{
root = tot = 0;
}
inline void insert(const vint v)
{
_insert(this->root, v);
}
inline vint erase(const vint v)
{
return _erase(this->root, v);
}
inline xint lower_rk(const vint v) const
{
return _lower_rk(this->root, v);
}
inline xint upper_rk(const vint v) const
{
return _upper_rk(this->root, v);
}
inline vint nth(const xint k) const
{
return _nth(this->root, k);
}
inline vint prev(const vint v) const
{
return _prev(this->root, v);
}
inline vint next(const vint v) const
{
return _next(this->root, v);
}
};
SBT<int> sbt;
int main()
{
_IO
sbt.init();
int T;
sc(T)
while (T--)
{
int op, v;
sc(op)sc(v)
switch (op)
{
case 1:
sbt.insert(v);
break;
case 2:
sbt.erase(v);
break;
case 3:
PRT(sbt.lower_rk(v)), PC(10);
break;
case 4:
PRT(sbt.nth(v)), PC(10);
break;
case 5:
PRT(sbt.prev(v)), PC(10);
break;
case 6:
PRT(sbt.next(v)), PC(10);
break;
}
}
return 0;
}
论文
最后贴出陈启峰巨巨的论文原文:https://wenku.baidu.com/view/a96cef5abe23482fb4da4cb1.html