news 2026/2/23 12:53:37

红黑树概述

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
红黑树概述

红黑树的概念:

什么是红黑树?简单来说,红⿊树是⼀棵⼆叉搜索树,他的每个结点增加⼀个存储位来表⽰结点的颜⾊,可以是红⾊或者⿊⾊。通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束,红⿊树确保没有⼀条路径会⽐其他路径⻓出2倍,因⽽是接近平衡的。
红黑树的规则

  1. 每个节点标识红色就是黑色
  2. 根节点是黑色的
  3. 如果一个结点是红色的,则它的两个孩子结点必须是黑色的,也就是说任意⼀条路径不会有连续的红⾊结点。
  4. 对于任意⼀个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的⿊⾊结点

那么我们如何保证红黑树的最长路径不超过最短路径的2倍的呢?
由规则4可知,从根到NULL结点的每条路径都有相同数量的⿊⾊结点,所以极端场景下,最短路径就是全是⿊⾊结点的路径,假设最短路径⻓度为bh(blackheight)。由规则2和规则3可知,任意⼀条路径不会有连续的红⾊结点,所以极端场景下,最⻓的路径就是⼀⿊⼀红间隔组成,那么最⻓路径的⻓度为2* bh。综合红⿊树的4点规则⽽⾔,理论上的全⿊最短路径和⼀⿊⼀红的最⻓路径并不是在每棵红⿊树都存在的。假设任意⼀条从根到NULL结点路径的⻓度为x,那么bh <= h <= 2 * bh。
**红黑树的效率:假设N是红⿊树树中结点数量,h最短路径的⻓度,那么h2 - 1 <= N < 2∗h - 1, 由此推出h ≈logN,也就是意味着红⿊树增删查改最坏也就是⾛最⻓路径2*logN,那么时间复杂度还是O(logN)。
红⿊树的表达相对AVL树要抽象⼀些,AVL树通过⾼度差直观的控制了平衡。红⿊树通过4条规则的颜⾊约束,间接的实现了近似平衡,他们效率都是同⼀档次,但是相对⽽⾔,插⼊相同数量的结点,红⿊树的旋转次数是更少的,因为他对平衡的控制没那么严格。

红黑树的实现:

红黑树的结构:
枚举值表示颜色:

enumColour{RED,BLACK};

树型与AVL树几乎完全一致,只是在其中加入了颜色的相关内容。这里的实现我们默认按照key/value的结构实现,如果有其他需求,可自行更改

template<classK,classV>structRBTreeNode{//这⾥更新控制平衡也要加⼊parent指针pair<K,V>_kv;RBTreeNode<K,V>*_left;RBTreeNode<K,V>*_right;RBTreeNode<K,V>*_parent;Colour _col;RBTreeNode(constpair<K,V>&kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr){}};

红黑树的插入:
红⿊树树插⼊⼀个值的⼤概过程:

  1. 插⼊⼀个值按⼆叉搜索树规则进⾏插⼊,插⼊后我们只需要观察是否符合红⿊树的4条规则。
  2. 如果是空树插⼊,新增结点是⿊⾊结点。如果是⾮空树插⼊,新增结点必须红⾊结点,因为⾮空树插⼊,新增⿊⾊结点就破坏了规则4,规则4是很难维护的。
  3. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是⿊⾊的,则没有违反任何规则,插⼊结束
  4. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是红⾊的,则违反规则3。进⼀步分析,c是红⾊,p为红,g必为⿊,这三个颜⾊都固定了,关键的变化看u的情况,需要根据u分为以下⼏种情况分别处理。

说明:下图中假设我们把新增结点标识为c(cur),c的⽗亲标识为p(parent),p的父亲标识为g(grandfather),p的兄弟标识为u(uncle)。
具体我们大致的分为3种情况:
情况一:变色
c为红,p为红,g为⿊,u存在且为红,则将p和u变⿊,g变红。在把g当做新的c,继续往上更新。分析:因为p和u都是红⾊,g是⿊⾊,把p和u变⿊,左边⼦树路径各增加⼀个⿊⾊结点,g再变红,相当于保持g所在⼦树的⿊⾊结点的数量不变,同时解决了c和p连续红⾊结点的问题,需要继续往上更新是因为,g是红⾊,如果g的⽗亲还是红⾊,那么就还需要继续处理;如果g的⽗亲是⿊⾊,则处理结束了;如果g就是整棵树的根,再把g变回⿊⾊。

g()g()/ \ / \p()u()-->p()u()/ /c()c()

情况二:单旋转+变色

g()/ \p()u(黑或不存在)/c()

c为红,p为红,g为⿊,u不存在或者u存在且为⿊,u不存在,则c⼀定是新增结点,u存在且为⿊,则c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上来的。
分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解决问题,需要旋转 + 变⾊。

调整后示例(以右旋为例):p()/ \c()g()\u(黑或不存在)

如果p是g的左,c是p的左,那么以g为旋转点进⾏右单旋,再把p变⿊,g变红即可。p变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为p的⽗亲是⿊⾊还是红⾊或者空都不违反规则。

p()/ \c()g()/ [原p的右子树]

如果p是g的右,c是p的右,那么以g为旋转点进⾏左单旋,再把p变⿊,g变红即可。p变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为p的⽗亲是⿊⾊还是红⾊或者空都不违反规则。
情况三:双旋转+变色
c为红,p为红,g为⿊,u不存在或者u存在且为⿊,u不存在,则c⼀定是新增结点,u存在且为⿊,则c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上来的。
分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解决问题,需要旋转+变⾊。

[g:黑] 修复操作 [p:黑] / \ ==========> / \ [p:红] [u:黑/不存在] [c:红] [g:红] / (情况1/2通用结果) [c:红]

代码实现:

template<classK,classV>classRBTree{typedefRBTreeNode<K,V>Node;public://右单旋voidRotateR(Node*parent){Node*subL=parent->_left;Node*subLR=subL->_right;//需要注意除了要修改孩⼦指针指向,还是修改⽗亲parent->_left=subLR;if(subLR)subLR->_parent=parent;Node*parentParent=parent->_parent;subL->_right=parent;parent->_parent=subL;// parent有可能是整棵树的根,也可能是局部的⼦树//如果是整棵树的根,要修改_root//如果是局部的指针要跟上⼀层链接if(parentParent==nullptr){_root=subL;subL->_parent=nullptr;}else{if(parent==parentParent->_left){parentParent->_left=subL;}else{parentParent->_right=subL;}subL->_parent=parentParent;}}//左单旋voidRotateL(Node*parent){Node*subR=parent->_right;Node*subRL=subR->_left;parent->_right=subRL;if(subRL)subRL->_parent=parent;Node*parentParent=parent->_parent;subR->_left=parent;parent->_parent=subR;if(parentParent==nullptr){_root=subR;subR->_parent=nullptr;}else{if(parent==parentParent->_left){parentParent->_left=subR;}else{parentParent->_right=subR;}subR->_parent=parentParent;}}voidInsert(constpair<K,V>&kv){if(_root==nullptr){_root=newNode(kv);_root->_col=BLACK;returntrue;}Node*parent=nullptr;Node*cur=_root;while(cur){if(kv.first<cur->_kv.first){parent=cur;cur=cur->_left;}elseif(kv.first>cur->_kv.first){parent=cur;cur=cur->_right;}else{returnfalse;}}cur=newNode(kv);cur->_col=RED;if(parent->_kv.first<kv.first)parent->_right=cur;elseif(parent->_kv.first>kv.first)parent->_left=cur;//链接父亲cur->_parent=parent;//父亲是红色,出现两个连续红色结点,违反规则三,调整while(parent&&parent->_col==RED){Node*grandFather=parent->_parent;if(parent==grandFather->_left){Node*uncle=grandFather->_right;// g// p u//情况一:叔叔存在且为红色,变色if(uncle&&uncle->_col==RED){//变色parent->_col=uncle->_col=BLACK;grandFather->_col=RED;//继续向上处理cur=grandFather;parent=cur->_parent;}// g//p u//情况二:叔叔存在且为黑,旋转+变色else{if(cur=parent->_left){// g// p u//c//单旋+变色RotateR(grandFather);parent->_col=BLACK;grandFather->_col=RED;}else{// g// p u// c//双旋+变色RotateL(parent);RotateR(grandFather);parent->_col=BLACK;grandFather->_col=RED;}break;}}else{// g//u pNode*uncle=grandFather->_left;// g// u p//情况一:叔叔存在且为红色,变色if(uncle&&uncle->_col==RED){//变色parent->_col=uncle->_col=BLACK;grandFather->_col=RED;//继续向上处理cur=grandFather;parent=cur->_parent;}// g//u p//情况二:叔叔存在且为黑,旋转+变色else{if(cur=parent->_right){// g// p u// c//单旋+变色RotateL(grandFather);parent->_col=BLACK;grandFather->_col=RED;}else{// g// p u// c//双旋+变色RotateR(parent);RotateL(grandFather);parent->_col=BLACK;grandFather->_col=RED;}break;}}}_root->_col=BLACK;returntrue;}private:Node*_root=nullptr;};

红黑树的查找:套用二叉搜索树的查找即可,时间复杂度为O(logN)

template<classK,classV>classBRTree{typedefRBTreeNode<K,V>Node;public:Node*Find(constK&key){Node*cur=_root;while(cur){if(cur->_kv.first<key){cur=cur->_right;}elseif(cur->_kv.first>key){cur=cur->_left;}else{returncur;}}returnnullptr;}private:Node*_root=nullptr;};

红黑树的验证:这⾥获取最⻓路径和最短路径,检查最⻓路径不超过最短路径的2倍是不可⾏的,因为就算满⾜这个条件,红⿊树也可能颜⾊不满⾜规则,当前暂时没出问题,后续继续插⼊还是会出问题的。所以我们还是去检查4点规则,满⾜这4点规则,⼀定能保证最⻓路径不超过最短路径的2倍。

  1. 规则1枚举颜⾊类型,天然实现保证了颜⾊不是⿊⾊就是红⾊。
  2. 规则2直接检查根节点即可
  3. 规则3前序遍历检查,遇到红⾊结点查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲的颜⾊就⽅便多了。
  4. 规则4前序遍历,遍历过程中⽤形参记录跟到当前结点的blackNum(⿊⾊结点数量),前序遍历遇到⿊⾊结点就++blackNum,⾛到空就计算出了⼀条路径的⿊⾊结点数量。再任意⼀条路径⿊⾊结点数量作为参考值,依次⽐较即可。
template<classK,classV>classBRTree{typedefRBTreeNode<K,V>Node;public:boolCheck(Node*root,intblackNum,constintrefNum){if(root==nullptr){//前序遍历⾛到空时,意味着⼀条路径⾛完了//cout << blackNum << endl;if(refNum!=blackNum){cout<<"存在⿊⾊结点的数量不相等的路径"<<endl;returnfalse;}returntrue;}// 检查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲就⽅便多了if(root->_col==RED&&root->_parent->_col==RED){cout<<root->_kv.first<<"存在连续的红⾊结点"<<endl;returnfalse;}if(root->_col==BLACK){blackNum++;}returnCheck(root->_left,blackNum,refNum)&&Check(root->_right,blackNum,refNum);}boolIsBalance(){if(_root==nullptr)returntrue;if(_root->_col==RED)returnfalse;//参考值intrefNum=0;Node*cur=_root;while(cur){if(cur->_col==BLACK){++refNum;}cur=cur->_left;}returnCheck(_root,0,refNum);}private:Node*_root=nullptr;};
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/22 21:23:22

BAAI/bge-m3物联网场景:设备日志语义异常检测系统

BAAI/bge-m3物联网场景&#xff1a;设备日志语义异常检测系统 1. 为什么传统日志分析在物联网里总是“力不从心” 你有没有遇到过这样的情况&#xff1a;工厂里上百台传感器每秒都在吐日志&#xff0c;告警邮件刷屏&#xff0c;但真正出问题的可能只有一条记录&#xff1b;运…

作者头像 李华
网站建设 2026/2/22 9:35:12

看完就想试!GLM-TTS打造的虚拟人物语音合集

看完就想试&#xff01;GLM-TTS打造的虚拟人物语音合集 你有没有听过这样一段语音—— 语调轻快&#xff0c;带着一丝川音的软糯&#xff0c;说&#xff1a;“今天这碗担担面&#xff0c;辣得刚刚好&#xff01;” 再换一段&#xff0c;声音沉稳温和&#xff0c;像一位老教师在…

作者头像 李华
网站建设 2026/2/22 15:44:28

软件I2C入门必看:手把手教你理解基本原理

以下是对您提供的博文《软件IC入门必看&#xff1a;手把手理解基本原理与工程实现》进行 深度润色与重构后的专业级技术文章 。本次优化严格遵循您的全部要求&#xff1a; ✅ 彻底去除AI痕迹&#xff0c;语言自然、老练、有“人味”——像一位在一线摸爬滚打十年的嵌入式老兵…

作者头像 李华
网站建设 2026/2/23 1:19:38

Qwen3-Embedding-0.6B在智能客服中的实际应用案例

Qwen3-Embedding-0.6B在智能客服中的实际应用案例 智能客服系统正从“关键词匹配规则引擎”迈向“语义理解上下文感知”的新阶段。但很多团队卡在关键一环&#xff1a;如何让机器真正读懂用户那句“我上个月的订单还没发货&#xff0c;物流单号是SF123456789”&#xff0c;而不…

作者头像 李华
网站建设 2026/2/23 6:50:24

小白友好!HeyGem数字人系统5分钟快速搭建实战

小白友好&#xff01;HeyGem数字人系统5分钟快速搭建实战 你是不是也遇到过这样的情况&#xff1a;想试试数字人视频生成&#xff0c;但看到“环境配置”“CUDA版本”“模型权重下载”就头皮发麻&#xff1f;想做个企业宣传视频&#xff0c;结果卡在部署环节一整天&#xff0c…

作者头像 李华
网站建设 2026/2/23 0:38:38

GLM-Image参数详解:宽度/高度非2的幂次(如1280×720)适配实测

GLM-Image参数详解&#xff1a;宽度/高度非2的幂次&#xff08;如1280720&#xff09;适配实测 1. 为什么非2的幂次分辨率值得专门测试&#xff1f; 你有没有试过在GLM-Image里输入1280720、19201080或者1366768这样的尺寸&#xff1f;点下生成按钮后&#xff0c;界面没报错&…

作者头像 李华