史上最快平衡树——红黑树 红黑树是一种二叉搜索树,单次操作复杂度上限$logn$,效率极高,基本用指针实现。 为了减小常数,红黑树的操作全部非递归实现。 下面系统介绍一下红黑树,包括复杂度的证明和基本操作。 1、红黑树的结构: 红黑树是二叉搜索树,满足BST性质,左儿子数值都小于当前节点,右儿子数值都大于当前节点,中序遍历单调递增。 红黑树根节点的父亲和叶节点的儿子指向同一个节点,称为红黑树的叶节点。 叶节点不仅具有哨兵的作用,也可以减少情况,简化代码,哨兵可以当作普通的黑色节点。 叶节点以外得点被称为红黑树的内节点。 每个节点有六个域,分别为$key$,$si$,$we$,$co$,$f$和$ch$,$key$代表当前点的权值,$we$代表当前值的个数,$si$代表子树大小,$ch$代表左右儿子,$f$代表父亲,$co$代表当前节点的颜色。 设$bh(x)$为节点$x$到叶节点的一条路径上的黑色节点数目,称为该节点的黑高,红黑树的黑高为根节点的黑高。 设$h(x)$为节点$x$到叶节点的所有路径中最长的一条的长度。 设$rt$代表红黑树的根。 一颗完整的红黑树如下图: 

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();//更新子树大小 } 旋转 左旋和右旋的时间复杂度为$O(1)$但常数极大,制约了平衡树的效率。 而红黑树通过红黑染色,减少旋转的次数,使得每次旋转的次数不超过$frac{2}{3}logn$,$n$为当前的红黑树大小,极大地优化了常数,这也是红黑树效率高的原因。 5、红黑树的查询 由于红黑树的插入和删除非常繁琐,我们在这里先讨论查询操作。 红黑树仅在插入和删除时会有结构的调整,其他情况下结构是固定的,可以和BST一样直接查询。 由于满足BST性质,所有查询都是从根开始。 1、查某个数$val$的排名: 如果当前节点权值大于$val$,查询左儿子即可; 如果当前节点权值小于$val$,把右子树大小和当前节点大小累加进答案,然后查询右儿子; 如果当前节点权值等于$val$,结束查询,答案加$1$(因为此时的答案为小于$val$的数的个数)。 2、查询排名为$rnk$的数: 如果当前节点左子树大小大于$rnk$,查询左儿子; 如果当前节点左子树大小和当前节点大小之和小于$rnk$,查询右儿子; 其余情况下结束查询,返回当前节点权值。 3、查找某一个数$val$: 如果当前节点权值大于$val$,查询左儿子; 如果当前节点权值小于$val$,查询右儿子; 如果当前节点权值等于$val$,结束查询,返回当前节点。 4、查询值$val$的前驱: 如果当前节点权值大于$val$,查询左儿子; 如果当前节点权值小于$val$,用当前点权值更新答案(取$max$),查询右儿子; 如果当前节点权值等于$val$,结束查询,返回答案。 5、查询值$val$的后继: 初始化答案为$inf$。 如果当前节点权值大于$val$,用当前点权值更新答案(取$minx$),查询左儿子; 如果当前节点权值小于$val$,查询右儿子; 如果当前节点权值等于$val$,结束查询,返回答案。 代码如下:
node *find(reg node *now,int key){//查找位置 for(;now->si&&now->key!=key;now=now->ch[now->key<key]); return now; } int rnk(int key){//查排名 reg int res,ans=0; for(reg node *now=rt;now->si;){ res=now->ch[0]->si; if(now->key==key) break; else if(now->key>key) now=now->ch[0]; else{ ans+=res+now->we;now=now->ch[1]; } } return ans+res+1; } int kth(int k){//查数 reg int res;reg node *now=rt; for(;now->si;){ res=now->ch[0]->si; if(k<=res) now=now->ch[0]; else if(res+1<=k&&k<=res+now->we) break; else{ k-=res+now->we;now=now->ch[1]; } } return now->key; } int pre(int key){//前驱 reg int res=0; for(reg node *now=rt;now->si;){ if(now->key<key){ res=now->key;now=now->ch[1]; } else now=now->ch[0]; } return res; } int nxt(int key){//后继 reg int res=0; for(reg node *now=rt;now->si;){ if(now->key>key){ res=now->key;now=now->ch[0]; } else now=now->ch[1]; } return res; } 查询 6、红黑树的插入: 如果树中有对应权值,那么问题很简单,找到对应权值,将路径上的节点$si$加一即可。 但是对于其他情况如何处理。 插入时会影响到红黑树的整体结构,破坏红黑树的性质。 为了维持优秀的复杂度及常数,我们需要对插入进行修正。 在插入的同时维护红黑树的性质并不简单,我们可以先找到一个位置,将新节点插进去,再对树进行调整。 插入代码如下:
inline void insert(int key){//插入节点 reg node *now=rt,*fa=nul;int pos; for(;now->si;now=now->ch[pos]){ now->si++;fa=now; pos=now->getpos(key); if(pos==-1){//找到对应值 now->we++;return; } } now=New(key);//找到位置,插入节点 if(fa->si) fa->ch[key>fa->key]=now; else rt=now; now->f=fa;insert_transfrom(now); } 插入 然后我们就可以进行繁琐的修正过程了。 首先对节点有如下定义: 

2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/78063.html

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();//更新子树大小 } 旋转 左旋和右旋的时间复杂度为$O(1)$但常数极大,制约了平衡树的效率。 而红黑树通过红黑染色,减少旋转的次数,使得每次旋转的次数不超过$frac{2}{3}logn$,$n$为当前的红黑树大小,极大地优化了常数,这也是红黑树效率高的原因。 5、红黑树的查询 由于红黑树的插入和删除非常繁琐,我们在这里先讨论查询操作。 红黑树仅在插入和删除时会有结构的调整,其他情况下结构是固定的,可以和BST一样直接查询。 由于满足BST性质,所有查询都是从根开始。 1、查某个数$val$的排名: 如果当前节点权值大于$val$,查询左儿子即可; 如果当前节点权值小于$val$,把右子树大小和当前节点大小累加进答案,然后查询右儿子; 如果当前节点权值等于$val$,结束查询,答案加$1$(因为此时的答案为小于$val$的数的个数)。 2、查询排名为$rnk$的数: 如果当前节点左子树大小大于$rnk$,查询左儿子; 如果当前节点左子树大小和当前节点大小之和小于$rnk$,查询右儿子; 其余情况下结束查询,返回当前节点权值。 3、查找某一个数$val$: 如果当前节点权值大于$val$,查询左儿子; 如果当前节点权值小于$val$,查询右儿子; 如果当前节点权值等于$val$,结束查询,返回当前节点。 4、查询值$val$的前驱: 如果当前节点权值大于$val$,查询左儿子; 如果当前节点权值小于$val$,用当前点权值更新答案(取$max$),查询右儿子; 如果当前节点权值等于$val$,结束查询,返回答案。 5、查询值$val$的后继: 初始化答案为$inf$。 如果当前节点权值大于$val$,用当前点权值更新答案(取$minx$),查询左儿子; 如果当前节点权值小于$val$,查询右儿子; 如果当前节点权值等于$val$,结束查询,返回答案。 代码如下: