当前位置: 首页 > news >正文

红黑树详解

一.红黑树介绍

1.红黑树的概念

红黑树是一颗二叉搜索树,增加了一个储存位来表示颜色,可以是红色或者黑色。通过对从根到叶子(指的是nullptr节点)的路径上每个节点颜色的约束,红黑树确保了没有一条路径会超出其他路径长度的两倍,因而接近平衡。

2.红黑树的规则

每条路径的结尾都是NIL节点(传统的叶节点,而是空节点,为了方便准确标识所有路径)
1.红黑树节点不是黑色就是红色;
2.整棵树的根节点一定是黑色;
3.如果一个节点是红色的,那么它的两个孩子必然是黑色的,也就是说,在红黑树的任意路径上没有连续的两个红节点;
4.对于任意节点,从该节点到其所有NULL节点的路径上,黑节点的数量相同。
注意:有些树上会补充NIL节点都是黑色的规则。后面讲解的细节中会忽略NIL。

为什么红黑树确保了没有一条路径会超出其他路径长度的两倍?

1.由规则3可得,最短路径(极端情况)的节点全是黑节点,设其高度为bh(black height);
2.为了让路径更长那么就得让路径上的红节点多一点,但不允许存在连续的红节点,那么路径最长时(极端情况)就是每一个黑节点接一个红节点,路径长为2 * bh;
3.综合而言,设红黑树的每条路径的长度为x,bh<= x <= 2 * bh;

3.红黑树的效率

设红黑树的节点个数为N,最短路径长为h,那么有2^h - 1 <= N < 2^(2 * h) - 1,所以h约等于logN,也就是说增删查改最多要走2*logN,故其效率是O(logN)。

相比于AVL树:
红黑树的表达相对于AVL树要抽象一点,AVL树通过对高度差的观察控制了平衡;而红黑树是通过四条规则约束,间接实现了近似平衡。它们的效率是同一档次的,但插入相同的数据时红黑树的旋转次数要少一点,因为它对平衡的控制相对轻松,而旋转消耗比较大,所以实际应用中红黑树是使用更多的。

二.红黑树的实现

1.红黑树的结构
enumColor{RED,BLACK};template<classk,classv>structRBTreeNode{typedefRBTreeNode<k,v>Node;RBTreeNode(constpair<k,v>&data=pair<k,v>()):_kv(data),_left(nullptr),_parent(nullptr),_right(nullptr),_col(BLACK){}pair<k,v>_kv;Node*_parent;Node*_left;Node*_right;Color _col;};template<classk,classv>classRBTree{typedefRBTreeNode<k,v>Node;public:private:Node*_root=nullptr;};
2.红黑树的插入
1.插入的大概过程

1.按照二叉搜索树的插入规则,插入后我们只需要观察它是否符合红黑树的四大规则;
2.如果是空树插入,那么插入的节点为根节点,且颜色为black;如果是非空树插入,那么插入的节点必须为red,因为插入black节点后会破坏规则4,它是很难维护的;
3.非空树插入新节点后,新节点为红色,如果其parent节点为black,则没有违反规则,插入结束;
4.非空树插入新节点后,新节点为红色,如果其parent节点为red,则会违反规则3,需要进一步分析。
重要节点说明:新插入的节点为cur( c ),其父节点为parent( p ),祖父节点为grandparent( g ),叔节点为uncle( u )。

新插入节点后,如果要进一步分析,那么四个重要节点中会有三个节点的颜色是知道的:
cur和parent都是red(它们导致要进一步分析),grandparent为black,只有uncle未知。以下的分析就是围绕uncle的不同来分类的

设bh为每条路径黑节点的数量

2.情况一:变色

uncle为红色:

分析:p、u都是红色,此时把它们变黑,g变红,这样解决了p、c连续红节点的问题,也没有改变以g为根的两条路径的bh,使得以g为根的子树符合红黑树的规则。但要注意的是,g变红了,如果g的parent是黑色,那么插入结束;如果是红色,那么就要让g变成c继续向上更新。
抽象图理解:这里只展示了p为g的左孩子的情况,插入右子树也差不多。

一直更新到g为根节点或它的oarent为black为止。
单变色要的条件
uncle存在且为red;

boolinsert(constpair<k,v>data){//先普通的插入if(!_root){_root=newNode(data);returntrue;}Node*cur=_root;Node*parent=nullptr;while(cur){if(data.first<cur->_kv.first){parent=cur;cur=cur->_left;}elseif(data.first>cur->_kv.first){parent=cur;cur=cur->_right;}elsereturnfalse;}cur=newNode(data);cur->_col=RED;cur->_parent=parent;if(data.first<parent->_kv.first)parent->_left=cur;elseparent->_right=cur;//调整节点的colwhile(parent&&parent->_col==RED)//更新到cur为根节点会自动跳出{Node*grandparent=parent->_parent;if(parent==grandparent->_left)//cur插入在左子树的情况{Node*uncle=grandparent->_right;if(uncle&&uncle->_col==RED)//情况1//uncle为红的情况,将parent和uncle置为黑,gp置为红{parent->_col=BLACK;uncle->_col=BLACK;grandparent->_col=RED;cur=grandparent;parent=cur->_parent;}}else//cur插入在右子树的情况{Node*uncle=grandparent->_left;if(uncle&&uncle->_col==RED){parent->_col=BLACK;uncle->_col=BLACK;grandparent->_col=RED;cur=grandparent;parent=cur->_parent;}}}_root->_col=BLACK;//保存根节点为黑returntrue;}
3.情况2:单旋+变色

当uncle为空或其_col为black时,单靠变色时无法解决问题的,还得加上旋转。
当u不存在时,cur一定时新插入的节点

如图假设u不存在时,cur不是新插入节点,因为都更新到这一步了,ABC肯定是存在黑节点的,但g的右子树没有黑节点,所以这种情况不存在,u为空,cur只能是新插入的节点

当u存在且颜色为黑时,cur一定不是新插入节点

假设cur为新插入节点:

明显可见,在插入cur前,以g为根的树的左右子树黑接待你个数就不同,所以u为黑,cur必不可能是新插入节点

抽象图分析:这里分析p为g的右孩子。

可见,如果p是g的右孩子,cur是p的右孩子,只需要以g为旋点左旋,最后
p:red->black
g:black->red
即可。
单旋 + 变色要的条件:
1.uncle不存在或存在且为black;
2.当p为g的左/右孩子时,cur为p的左/右孩子。

while(parent&&parent->_col==RED){Node*grandparent=parent->_parent;if(parent==grandparent->_left){Node*uncle=grandparent->_right;if(uncle&&uncle->_col==RED)//uncle为红的情况,将parent和uncle置为黑,gp置为红{parent->_col=BLACK;uncle->_col=BLACK;grandparent->_col=RED;cur=grandparent;parent=cur->_parent;}elseif(uncle==nullptr||uncle->_col==BLACK){if(cur==parent->_left)//单旋接变色//情况2{// g// p u//cRotateR(grandparent);parent->_col=BLACK;grandparent->_col=RED;}break;}}else//p == gp->r{Node*uncle=grandparent->_left;if(uncle&&uncle->_col==RED){parent->_col=BLACK;uncle->_col=BLACK;grandparent->_col=RED;cur=grandparent;parent=cur->_parent;}elseif(uncle==nullptr||uncle->_col==BLACK){if(cur==parent->_right)//单旋接变色{// g// u p// cRotateL(grandparent);parent->_col=BLACK;grandparent->_col=RED;}break;}}}//左右单旋:voidRotateR(Node*parent){Node*subL=parent->_left;Node*subLR=subL->_right;Node*Pparent=parent->_parent;parent->_left=subLR;parent->_parent=subL;if(subLR)subLR->_parent=parent;if(parent==_root)_root=subL;subL->_right=parent;subL->_parent=Pparent;if(Pparent&&Pparent->_left==parent)Pparent->_left=subL;elseif(Pparent)Pparent->_right=subL;}voidRotateL(Node*parent){Node*subR=parent->_right;Node*subRL=subR->_left;Node*Pparent=parent->_parent;parent->_right=subRL;parent->_parent=subR;if(subRL)subRL->_parent=parent;if(parent==_root)_root=subR;subR->_left=parent;subR->_parent=Pparent;if(Pparent&&Pparent->_left==parent)Pparent->_left=subR;elseif(Pparent)Pparent->_right=subR;}
4.情况3:双旋+变色

抽象图分析:p为g的右孩子。

可见当p为g的右孩子,cur为p的左孩子时,得先以p为旋点右旋,在以p为旋点左旋,最后进行变色处理:
g:black->red;
c:red->black;
双旋 + 变色的条件
1.uncle不存在/存在且为黑;
2.p为g的右/左孩子时,c为p的左/右孩子;

while(parent&&parent->_col==RED){Node*grandparent=parent->_parent;if(parent==grandparent->_left){Node*uncle=grandparent->_right;if(uncle&&uncle->_col==RED)//uncle为红的情况,将parent和uncle置为黑,gp置为红{parent->_col=BLACK;uncle->_col=BLACK;grandparent->_col=RED;cur=grandparent;parent=cur->_parent;}elseif(uncle==nullptr||uncle->_col==BLACK){if(cur==parent->_left)//单旋接变色{// g// p u//cRotateR(grandparent);parent->_col=BLACK;grandparent->_col=RED;}else//双旋接变色{// g// p u// cRotateL(parent);RotateR(grandparent);cur->_col=BLACK;grandparent->_col=RED;}break;}}else//p == gp->r{Node*uncle=grandparent->_left;if(uncle&&uncle->_col==RED){parent->_col=BLACK;uncle->_col=BLACK;grandparent->_col=RED;cur=grandparent;parent=cur->_parent;}elseif(uncle==nullptr||uncle->_col==BLACK){if(cur==parent->_right)//单旋接变色{// g// u p// cRotateL(grandparent);parent->_col=BLACK;grandparent->_col=RED;}else//双旋接变色{// g// u p// cRotateR(parent);RotateL(grandparent);cur->_col=BLACK;grandparent->_col=RED;}break;}}}
5.插入的完整代码
boolinsert(constpair<k,v>data){//先普通的插入if(!_root){_root=newNode(data);returntrue;}Node*cur=_root;Node*parent=nullptr;while(cur){if(data.first<cur->_kv.first){parent=cur;cur=cur->_left;}elseif(data.first>cur->_kv.first){parent=cur;cur=cur->_right;}elsereturnfalse;}cur=newNode(data);cur->_col=RED;cur->_parent=parent;if(data.first<parent->_kv.first)parent->_left=cur;elseparent->_right=cur;//调整节点的colwhile(parent&&parent->_col==RED){Node*grandparent=parent->_parent;if(parent==grandparent->_left){Node*uncle=grandparent->_right;if(uncle&&uncle->_col==RED)//uncle为红的情况,将parent和uncle置为黑,gp置为红{parent->_col=BLACK;uncle->_col=BLACK;grandparent->_col=RED;cur=grandparent;parent=cur->_parent;}elseif(uncle==nullptr||uncle->_col==BLACK){if(cur==parent->_left)//单旋接变色{// g// p u//cRotateR(grandparent);parent->_col=BLACK;grandparent->_col=RED;}else//双旋接变色{// g// p u// cRotateL(parent);RotateR(grandparent);cur->_col=BLACK;grandparent->_col=RED;}break;}}else//p == gp->r{Node*uncle=grandparent->_left;if(uncle&&uncle->_col==RED){parent->_col=BLACK;uncle->_col=BLACK;grandparent->_col=RED;cur=grandparent;parent=cur->_parent;}elseif(uncle==nullptr||uncle->_col==BLACK){if(cur==parent->_right)//单旋接变色{// g// u p// cRotateL(grandparent);parent->_col=BLACK;grandparent->_col=RED;}else//双旋接变色{// g// u p// cRotateR(parent);RotateL(grandparent);cur->_col=BLACK;grandparent->_col=RED;}break;}}}_root->_col=BLACK;//保存根节点为黑returntrue;}

在插入过程中,红黑树靠什么增加bh大小的?
单看情况1、2、3每执行一次bh的大小时不变的。在插入过程中,其大小的变化只会由一种情况引起,那就是在变色环节:while (parent && parent->_col == RED)中一直更新到根节点,且当g为根节点时,u为red,这是会按情况1执行,让根变为red后跳出循环,再在循环外让根变黑,这时bh就增加了1。

三.红黑树的检验

判断一棵树是否为红黑树只要看它是否满足红黑树的四条规则就好了:

template<classk,classv>classRBTree{typedefRBTreeNode<k,v>Node;public:boolisbalance(){if(_root==nullptr)returntrue;if(_root->_col==RED)returnfalse;intway=0;Node*cur=_root;while(cur)//获取单条路径的bh{if(cur->_col==BLACK)++way;cur=cur->_left;}returncheck(_root,0,way);}boolcheck(Node*root,intblacknum,constintrefnum)//refnum为每条路径的黑节点个数{if(root==nullptr){if(blacknum==refnum)//代码走到着时就说明一条路走完了returntrue;else{cout<<"有路径的黑色节点数不相等"<<endl;returnfalse;}}if(root->_col==BLACK)blacknum++;else//r->c = red//检查子树不太方便,因为有两个,而且还不一定存在,所以检查parent{if(root->_parent&&root->_parent->_col==RED)returnfalse;}returncheck(root->_left,blacknum,refnum)&&check(root->_right,blacknum,refnum);}
http://www.jsqmd.com/news/858655/

相关文章:

  • 北京雅思写作提分机构排名!2026靠谱榜单出炉 - 品牌测评鉴赏家
  • 2026年5月最新 环保监测十大口碑品牌排名 - 水质仪表品牌排行榜
  • 2026上海健身教练培训机构推荐:5家高性价比靠谱机构全解析 - 品牌2025
  • 2026年常州热缩管源头厂家深度横评:从新能源防护到军工定制化解决方案全景对标 - 精选优质企业推荐官
  • 一句话看懂 vibing-steampunk
  • 如何快速管理Android设备:Fastboot Enhance完整指南
  • 一张图看懂中画幅vs全画幅AI渲染差异:2024 Q2 Midjourney V6.1 GPU显存占用热力图实测对比
  • 2026求职新范式:深度测评鹅来面OfferGoose,你的AI求职“第二大脑”
  • 2026高性价比普拉提培训机构推荐:靠谱又划算怎么选? - 品牌2025
  • 面膜哪个牌子美白补水效果最好?面膜口碑最好的前十名,润养舒缓长效维稳 - 博客万
  • 从 Claude Code 到 SAP ADT,vibing-steampunk 把 ABAP 开发带进 Agentic 工程现场
  • 金华润富黄金回收深度解析 - 润富黄金珠宝行
  • 能预防口腔溃疡的牙膏哪家好你想知道的都在这了 - 速递信息
  • OpenModScan:工业自动化领域的专业Modbus调试工具终极指南
  • 微信批量发送终极指南:三步实现高效自动化消息群发
  • 每天被短视频偷走时间?我做了个会“打断你分心”的 Chrome 插件:PauseCat
  • Qt 高级开发 009: C++ Lambda 表达式
  • macOS光标个性化深度指南:Mousecape技术解析与实战应用
  • 视频怎么转文字?2026年视频转文字工具方法盘点及推荐 - 软件小管家
  • LeagueAkari技术深度解析:基于LCU API的英雄联盟客户端工具集架构设计与实现
  • RAG 检索增强系统:从原理到实战的完整指南
  • 木塑地板厂家技术实力盘点:核心维度对比解析 - 奔跑123
  • 2026国产EDA怎么选?GPU国产芯片封装设计软件方案推荐看这篇 - 品牌2025
  • 交换与路由技术整理与总结(持续更新版)
  • Minecraft多版本管理的终极解决方案:Prism Launcher深度解析
  • 国内装修水管品牌排行:5家实力企业深度解析 - 奔跑123
  • 25+干皮抗老精华油怎么选?干敏肌必读的4个核心要点 - 新闻快传
  • 由C++速通C#
  • 净化通风赛道:2026空气过滤器、防火阀、传递窗厂优选参考 - 深度智识库
  • 如何在Windows上打造你的终极终端体验:告别枯燥命令行的完整指南