红黑树是一种二叉搜索树,单次操作复杂度上限$logn$,效率极高,基本用指针实现。
为了减小常数,红黑树的操作全部非递归实现。
下面系统介绍一下红黑树,包括复杂度的证明和基本操作。
1、红黑树的结构:
红黑树是二叉搜索树,满足BST性质,左儿子数值都小于当前节点,右儿子数值都大于当前节点,中序遍历单调递增。
红黑树根节点的父亲和叶节点的儿子指向同一个节点,称为红黑树的叶节点。
叶节点不仅具有哨兵的作用,也可以减少情况,简化代码,哨兵可以当作普通的黑色节点。
叶节点以外得点被称为红黑树的内节点。
每个节点有六个域,分别为$key$,$si$,$we$,$co$,$f$和$ch$,$key$代表当前点的权值,$we$代表当前值的个数,$si$代表子树大小,$ch$代表左右儿子,$f$代表父亲,$co$代表当前节点的颜色。
设$bh(x)$为节点$x$到叶节点的一条路径上的黑色节点数目,称为该节点的黑高,红黑树的黑高为根节点的黑高。
设$h(x)$为节点$x$到叶节点的所有路径中最长的一条的长度。
设$rt$代表红黑树的根。
一颗完整的红黑树如下图:
2、红黑树的性质:
一颗完整的红黑树具有以下性质:
1、每个节点都是红色或黑色;
2、根节点和叶节点是黑色;
3、红色节点的儿子都是黑色;
4、从根节点到叶节点的每条路径上经过的黑色节点数相同。
这些性质保证了红黑树的优秀复杂度。
3、红黑树复杂度的证明:
我们要证明红黑树的时间复杂度为$O(logn)$。
引理一:红黑树中没有任何一条从根节点到叶节点的路径比另一条长出一倍。
证明:
从每个节点到叶子的路径上的黑色节点数称为该节点的黑高度,记为$bh$。
根据性质4,根节点到叶节点的每条路径上黑色节点数相同;再根据性质3,红色节点的儿子都是黑色。
把根节点到叶节点的路径看成一个序列,把红色节点插入到黑色节点之间,则没有两个红色节点相邻。
所以一条路径上红色节点数不会超过黑色节点数,没有任何一条路径比另一条长出一倍。
证毕。
引理二:以$x$为根的子树中至少包含$2^{hb(x)}-1$个内节点。
证明:
用数学归纳法证明。
如果$hb(x)=0$,$x$一定是叶节点,结论显然成立。
对于其他节点,每个节点都有两个儿子,根据儿子的颜色,每个儿子的黑高为$hb(x)$或$hb(x)-1$,并且儿子节点的黑高都小于当前节点的黑高。
根据前一布的归纳可以得出,以儿子节点为根的子树内至少有$2^{hb(x)-1}-1$个节点。
所以最次情况下,即两个儿子的黑高都是$hb(x)-1$的情况下,当前子树大小可以去得最小值$2^{hb(x)}-1$,其余情况下均大于这个值。
证毕。
引理三:一颗含有$n$个节点的红黑树,高度至多为$2log(n+1)$。
证明:
根据性质3和性质4,从根到叶节点的其中一条简单路径上黑色节点占一半以上。
所以$hb(x)>=\frac{h(x)}{2}$,当$x$为根时,根据引理二,可以得到不等式:
$n>=2^{\frac{h}{2}}-1$
移项后取对数可得:
$h<=2lg(n+1)$
证毕。
4、旋转:
旋转是红黑树的必要操作。
红黑树的旋转和其他的带旋平衡树类似,会带旋treap,splay,AVL或SBT的大佬可以选择跳过。
旋转分为左旋和右旋,都是在维护BST性质下对树的结构进行的局部调整。
记住是局部调整,对红黑树的其他位置都没有影响。
定义$rotate(x,p)$表示以$x$的父亲为支点左/右旋,0代表右旋,1代表左旋。
一张图理解一下:
旋转要保证$x$和$y$均不为空(均不是哨兵),对于$\alpha$,$\beta$和$\gamma$则没有特殊要求。
以0代表左儿子,以1代表右儿子,就是$p^1$儿子过继给父亲,原来的祖父变为父亲,原来的父亲变为儿子。
左旋和右旋改变的仅有父指针和儿子指针,以及pushup时更新的子树大小,节点的其他域都没有改变。
经过旋转,可以调节红黑树的整体结构,使其更加平衡。
代码如下:
void rotate(node *now,int pos){//旋转操作 node *c=now->ch[pos^1]; now->ch[pos^1]=c->ch[pos]; if(c->ch[pos]->si) c->ch[pos]->f=now; c->f=now->f; if(!now->f->si) rt=c;//旋到根 else now->f->ch[now->f->ch[0]!=now]=c; c->ch[pos]=now;now->f=c;c->si=now->si; now->pushup();//更新子树大小 }