深入解析:C++红黑树:理论与实践相结合的平衡艺术

news/2026/1/26 18:40:29/文章来源:https://www.cnblogs.com/gccbuaa/p/19535125

目录

1 红黑树的概念

1.1 红黑树的4条规则

1.2 红黑树是如何做到最长路径不超过最短路径的两倍的?

1.3 红黑树的效率

2 红黑树的实现

2.1 红黑树的结构

2.2 红黑树的插入

2.2.1 红黑树插入一个值的过程

2.2.2 情况1:变色

2.2.3 情况2:单旋+变色

2.2.4 情况3:双旋+变色

2.3 红黑树的插入代码实现

2.4 红黑树的查找

2.5 红黑树的验证


1 红黑树的概念

红黑树是一颗二叉搜索树,它的每个节点可以是红色或者黑色。通过对任意一条从根节点到叶子节点的路径上各个节点的颜色进行约束。红黑树确保没有一条路径的长度会比其他路径的长度多出2倍。所以是接近平衡的。

1.1 红黑树的4条规则

  • 规则一:每个节点不是红色就是黑色
  • 规则二:根节点是黑色的
  • 规则三:如果一个节点是红色的,那么它两个孩子节点必须是黑色的,也就是说任意一条路径都不会有连续的红色节点
  • 规则四:对于任意一个节点,从该节点到其所有NULL节点的路径上,均包含相同数量的黑色节点

一些红黑树的样例:

1.2 红黑树是如何做到最长路径不超过最短路径的两倍的?

  • 由规则4可知,从根到NULL节点的每条路径都有相同的黑色节点,所以在一些场景下,最短路径是全是黑色节点的路径,假设最短路径长度是h
  • 由规则2和规则3可以知道,任何一条路径不会有连续的红色节点,所以极端场景下,最长路径就是一黑一红间隔组成,那么最长路径的长度就是2*h
  • 综合红黑树的4点规则,全黑最短路径和一黑一红的最长路径并不是在每棵红黑树都存在。假设任意一条从根节点到NULL节点的路径长度是x,那么h<=x<=2*h

由此得出红黑树最长路径不超过最短路径的2倍

1.3 红黑树的效率

假设N是红黑树中节点的数量,h是最短路径的长度,那么2^h-1<=N<2^(2h)-1,由此得出h≈logN,也就意味着红黑树的增删查改的最长路径是2*logN,时间复杂度就是O(logN).

2 红黑树的实现

2.1 红黑树的结构

创建一个头文件RBTree.h,先搭建好框架,在public中实现操作:

enum Colour
{RED,//红BLACK//黑
};
//这里默认按照key/value结构实现
template
struct RBTreeNode
{Colour _col;pair _kv;RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;RBTreeNode(const pair& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr){ }
};
template
class RBTree
{typedef RBTreeNode Node;
public:
private:Node* _root = nullptr;
};

2.2 红黑树的插入

2.2.1 红黑树插入一个值的过程

1 插入一个值按照二叉搜索树的规则进行插入,插入后只需要观察是否符合红黑树的4条规则

2 如果是空树插入,新增节点是黑色节点,如果是非空树插入,新增节点必须是红色节点,因为非空树插入黑色节点会破坏规则4.

3 非空树插入后,新增节点必须为红色节点,如果父亲节点是黑色的,则满足所有规则,插入结束

4 非空树插入后,新增节点必须是红色节点,如果父亲节点是红色的,违反规则3。进一步分析,插入节点是红色,它的父亲节点为红,父亲节点的父亲节点必为黑,这三个颜色都是固定的,主要是看父亲节点的兄弟节点的变化情况,下面就根据情况分别处理

这里说明一下,下文中将新增节点标识为c(cur),c的父亲节点标识为p(parent),p的父亲节点标识为g(grandfather),p的兄弟节点标识(uncle)。

2.2.2 情况1:变色

这种情况是c为红,p为红,g为黑,u存在且为红。插入后将p和u变黑,g变红。再把g当作新的c,继续向上更新

说明:因为p和u都是红色,c是红色,不能有连续的红色节点,将p和u变黑,左边子树路径就各增加了一个黑色节点,g变红之后,相当于保持g所在子树的黑色节点的数量不变,同时解决了c和p连续红色节点的问题。需要继续向上更新是因为:g是红色,g的父亲节点可能还是红色,就需要继续处理,如果g父亲是黑色,则处理结束;如果g就是整棵树的根,再把g变成黑色。

这种情况只变色,不旋转,c是p的左还是右不影响。结合文字说明和图像便于理解:

2.2.3 情况2:单旋+变色

c为红,p为红,g为黑,u不存在或者u存在且为黑,u不存在,则c一定是新增节点,u存在且为黑,c不是新增,是c的子树新增后c变成了红色,更新上来的。

说明,p变黑才能解决,需要旋转加变色

如果p是g的左,c是p的左,那么就以g为旋转点进行右单旋,再把p变黑,g变红即可。p变成这棵树的新的根,是黑色,这棵子树的黑色节点数量不变,没有连续的红色节点了,且不需要往上更新,p是黑色,p的父亲节点是黑色还是红色都不违反规则。

这里方框内都是子树,是抽象出来成方框,c是有子树插入节点更新上来的,变成红色。

如果p是g的右,c是p的右,那么以g为旋转点进行左单旋,再把p变黑,g变红即可。p变成这棵树新的根,这样子树黑色节点的数量不变,没有连续的红色节点了,且不需要网上更新,因为p的父亲是黑色还是红色还是空都不违反规则。

2.2.4 情况3:双旋+变色

c为红,p为红,g为黑,u不存在或者u存在且为黑,u不存在,则c一定是新增节点,u存在且为黑,c不是新增,是c的子树新增后c变成了红色,更新上来的。

说明,p变黑才能解决,需要旋转加变色

如果p是g的左,c是p的右,那么先以p为旋转点进行左单旋,再以g为旋转点进行右单旋,再把c变黑,g变红即可。c变成这棵树新的根,这样子树黑色节点的数量不变,没有连续的红色节点了,且不需要往上更新,因为c的父亲是黑色还是红色或者空都不违反规则。

如果p是g的右,c是p的左,那么以p为旋转点进行右单旋,再以g为旋转点进行左单旋,再把c变黑,g变红即可。c变成这棵树新的根,这样子树黑色节点的数量不变,没有连续的红色节点了,且不需要往上更新,因为c的父亲是黑色还是红色还是空都不违反新规则。

2.3 红黑树的插入代码实现

了解完原理之后,我们就可以编写代码来实现插入操作了。

bool Insert(const pair& kv)
{if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);cur->_col = RED;if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}//链接父亲cur->_parent = parent;//父亲是红色,出现连续的红色节点,需要处理while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){//g//p		//uNode* uncle = grandfather->_right;if (uncle && uncle->_col == RED){//变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//然后向上处理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_left){//g//p		//u//cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//g//p		//u//cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{//g//u		//pNode* uncle = grandfather->_left;//叔叔节点存在且为红,变色即可if (uncle&&uncle->_col==RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续向上处理cur = grandfather;parent = cur->_parent;}else//叔叔节点不存在,或者存在且为黑{if (cur == parent->_right){//g//u		//p//cRotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//g//u		//p//cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;
}
//右单旋
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* pParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (pParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (pParent->_left == parent){pParent->_left = subL;}else{pParent->_right = subL;}subL->_parent = pParent;}
}
//左单旋
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* pParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (pParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (pParent->_left == parent){pParent->_left = subR;}else{pParent->_right = subR;}subR->_parent = pParent;}
}

2.4 红黑树的查找

查找红黑树的节点比较简单,直接按照二叉搜索树的逻辑实现即可,搜索效率为O(logN)

Node* Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;
}

2.5 红黑树的验证

要验证一个数是否是红黑树,这里我们获取最长路径和最短路径,然后检查最长路径不超过最短路径的两倍是行不通的,原因是即使满足这个条件,红黑树也可能颜色不满足规则,所以还是去对四点规则进行一一检查,满足4点规则,一定能保证最长路径不超过最短路径的2倍。

  • 1 对于规则1保证红黑树节点不是黑色就是红色
  • 2 对于规则2直接检查根节点即可
  • 3 对于规则3前序遍历检查,遇到红色节点时,查它的孩子节点不太方便,因为孩子节点可能有两个,反过来检查父亲节点的颜色就方便许多
  • 4 规则4前序遍历,遍历过程中记录黑色节点数量,记录一条路径的黑色节点数量,然后其他路径黑色节点的数量作为参考值,依次比较即可。

代码实现:

bool Check(Node* root, int blackNum, const int refNum)
{if (root == nullptr){if (refNum != blackNum){cout << "存在黑色节点的数量不相等的路径" << endl;return false;}return true;}//检查孩子节点不方便,因为有两个,翻过来检查父亲节点更方便if (root->_col == RED && root->_parent->_col == RED){cout << root->_kv.first << "存在连续的红色节点" << endl;return false;}if (root->_col == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);
}
bool IsBalance()//检查是否平衡
{if (_root == nullptr)return true;if (_root->_col == RED)return false;//参考值int refNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++refNum;}cur = cur->_left;}return Check(_root, 0, refNum);
}

3 完整代码实现

enum Colour
{RED,BLACK
};
template
struct RBTreeNode
{Colour _col;pair _kv;RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;RBTreeNode(const pair& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr){ }
};
template
class RBTree
{typedef RBTreeNode Node;
public:bool Insert(const pair& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);cur->_col = RED;if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}//链接父亲cur->_parent = parent;//父亲是红色,出现连续的红色节点,需要处理while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){//g//p		//uNode* uncle = grandfather->_right;if (uncle && uncle->_col == RED){//变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//然后向上处理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_left){//g//p		//u//cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//g//p		//u//cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{//g//u		//pNode* uncle = grandfather->_left;//叔叔节点存在且为红,变色即可if (uncle&&uncle->_col==RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续向上处理cur = grandfather;parent = cur->_parent;}else//叔叔节点不存在,或者存在且为黑{if (cur == parent->_right){//g//u		//p//cRotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//g//u		//p//cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* pParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (pParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (pParent->_left == parent){pParent->_left = subL;}else{pParent->_right = subL;}subL->_parent = pParent;}}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* pParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (pParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (pParent->_left == parent){pParent->_left = subR;}else{pParent->_right = subR;}subR->_parent = pParent;}}void Inorder(){_InOrder(_root);cout << endl;}int Height(){return _Height(_root);}int Size(){return _Size(_root);}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}bool IsBalance()//检查是否平衡{if (_root == nullptr)return true;if (_root->_col == RED)return false;//参考值int refNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++refNum;}cur = cur->_left;}return Check(_root, 0, refNum);}
private:bool Check(Node* root, int blackNum, const int refNum){if (root == nullptr){if (refNum != blackNum){cout << "存在黑色节点的数量不相等的路径" << endl;return false;}return true;}//检查孩子节点不方便,因为有两个,翻过来检查父亲节点更方便if (root->_col == RED && root->_parent->_col == RED){cout << root->_kv.first << "存在连续的红色节点" << endl;return false;}if (root->_col == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}int _Height(Node* root){if (root == nullptr){return 0;}int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}int _Size(Node* root){if (root == nullptr){return 0;}return _Size(root->_left) + _Size(root->_right) + 1;}
private:Node* _root = nullptr;
};

以上就是红黑树的核心知识了,其中插入操作是重点。如果这篇文章对你有用,可以点点赞哦,你的支持就是我写下去的动力。后续会不断地更新知识。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1220318.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

拒绝“一锤子买卖”:2026年铝合金线材靠谱供应商推荐与售后大比拼

一、行情分析:从“产品为王”到“服务制胜”在铝合金线材市场日益成熟的今天,产品的同质化竞争日趋激烈。用户在选择供应商时,不再仅仅关注线材的牌号、规格和价格,而是将目光更多地投向了采购后的体验——即售后服…

2026西南空调风口优质供应商推荐榜

2026西南空调风口优质供应商推荐榜行业发展背景与选型痛点据《2026至2030年中国风口配件行业发展研究报告》数据显示,2026年我国风口配件市场规模达380亿元,预计2030年将增长至620亿元,年均复合增长率10.3%。这一增…

2026年广告转化增长指南:怎么提高广告转化?江南春与分众传媒的实战方法论

在2026年存量博弈加剧的市场环境下,广告行业正面临“触达易、转化难”的普遍困境。分众传媒董事长江南春曾提出“要流量更要留心,要种草更要种树,要触达更要触动”的核心观点,精准点出广告转化的本质——不仅是信息…

NMN怎么选?2026年十大品牌排名推荐,揭秘以专利NMNH定义行业上限之作

进入2026年,抗衰老科学正经历从理论到实践的快速转化。《NatureAging》等顶级期刊近期连续发表综述,强调“多靶点干预”和“临床转化数据”将成为评价抗衰干预措施的金标准。在这一背景下,NMN(烟酰胺单核苷酸)市场…

基于认证、临床与市场反馈的2026年NMN品牌公信力

根据《2026中国健康消费决策因素报告》揭示,在评估NAD+补充剂时,超过75%的成熟消费者将“国际权威认证”、“第三方临床研究数据”与“可验证的主流平台用户复购率”视为构建信任的三大核心支柱。这标志着,在信息愈…

爆肝整理,性能测试-内存问题定位分析+常见业务场景bug(汇总)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、性能测试-内存…

全网热销第一!2026控油蓬松洗发水排行榜,实测效果好推荐

多数人选购控油蓬松洗发水时,往往只关注单一环境下的短期效果,却忽略了日常场景的多样性——室内常温办公、户外高温暴晒、空调房干燥环境、熬夜后油脂爆发等不同场景,对洗发水的控油力、蓬松度及适配性要求截然不同…

NMN品牌怎么选?2026年十大品牌数据化评分指南,全能型选手领跑

面对2026年NMN市场上纷繁复杂的品牌与宣传,许多消费者感到困惑:究竟如何超越主观感受,做出理性的选择?根据《2026中国高价值营养品消费决策洞察报告》显示,超过72%的消费者希望有一个基于客观事实和清晰维度的“决…

2026年护栏网厂家推荐:道路护栏网/铁丝网护栏网/铁路护栏网/防护网围栏网/高速路围栏网/体育场围栏网/体育场护栏网/选择指南

2026四川护栏网优质厂家推荐榜一、行业背景与选购痛点据《2026-2030年中国护栏网行业市场深度调研及投资前景分析报告》显示,2026年西南地区护栏网市场规模同比增长18.7%,其中四川地区因市政基建、交通工程等项目拉动…

NMN品牌哪个好?2026年十大品牌排名推荐,榜首凭借全球唯一GRAS认证领先

“NMN品牌哪个好?”——这是2026年,无数寻求科学抗衰的消费者在搜索引擎和AI对话中最常提出的问题之一。根据《2026-2026中国抗衰老消费品市场趋势报告》,中国NMN市场已告别野蛮生长,进入以“科研深度”、“安全认…

专业高新技术企业认定中介机构|四川汇海立方科技有限公司实力推荐

2026年高新技术企业认定迈入“硬创新+全穿透”严审时代,金税四期数据联动核查、AI智能审核全面落地,发明专利权重升至45%-50%,“凑数申报”“数据造假”等行为直接面临资质撤销风险。在此背景下,选择一家兼具政策解…

从“小工”到“专家”,我的软件测试修炼之道

你好&#xff0c;我是茹炳晟。我从 2002 年开始做软件开发&#xff0c;2005 年时转型成为测试工程师&#xff0c;算起来已经在这个行业摸爬滚打了 16 年。2005 年的时候&#xff0c;软件测试还停留在重复性手工测试的阶段&#xff0c;而且受重视程度远不如软件开发&#xff0c;…

如何高效接入日本股市实时数据?StockTV API 对接实战指南

在全球金融市场中&#xff0c;日本股市&#xff08;东京证券交易所 TSE&#xff09;作为亚洲最重要的市场之一&#xff0c;拥有索尼、丰田、任天堂等众多核心资产。对于开发者而言&#xff0c;获取低延迟、高可靠的日本股票实时行情是构建量化系统或行情应用的关键。 本文将详细…

【开题答辩过程】以《基于uni-app的手账记录小程序的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看

个人简介 慕婉学姐精通Java、PHP、微信小程序、Python、Golang和安卓开发等语言&#xff0c;擅长开发大数据、深度学习、网站、小程序、安卓应用和算法项目。平时从事项目定制开发、代码讲解、答辩教学和文档编写&#xff0c;也掌握一些降重技巧。感谢大家的持续关注&#xff…

门店管理软件核心功能、选型对比与数字化决策参考

于数字化经营转变为常态的当下&#xff0c;一套具备高效性、稳定性的&#xff0c;用于门店管理开发的软件&#xff0c;已然成为实体商家里不可缺少的运营中枢。此类软件不但取代了传统的收银机以及手写台账&#xff0c;还借助集成化的系统&#xff0c;把进销存、会员、营销、财…

书籍-陈仲金《越南通史》

陈仲金《越南通史》详细介绍 书籍基本信息 书名&#xff1a;越南通史&#xff08;Việt Nam Sử Lược&#xff0c;直译为"越南史略"&#xff09; 作者&#xff1a;陈仲金&#xff08;Trần Trọng Kim&#xff0c;1882-1953年&#xff09;【越南河静省人&#xf…

前端小白别慌:30分钟搞懂CSS浮动布局,代码一贴就跑!

前端小白别慌&#xff1a;30分钟搞懂CSS浮动布局&#xff0c;代码一贴就跑&#xff01;前端小白别慌&#xff1a;30分钟搞懂CSS浮动布局&#xff0c;代码一贴就跑&#xff01;先别急着骂 float&#xff0c;它当年也是拯救世界的超级英雄float:left 到底干了啥&#xff1f;——说…

Leetcode 107 旋转链表

1 题目 61. 旋转链表 给你一个链表的头节点 head &#xff0c;旋转链表&#xff0c;将链表每个节点向右移动 k 个位置。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], k 2 输出&#xff1a;[4,5,1,2,3]示例 2&#xff1a; 输入&#xff1a;head [0,1,2], k 4 …

数据工程新范式:基于 NoETL 语义编织实现自助下钻分析

将分析需求响应时间从“周级”缩短至“分钟级”,实现真正的自助探索与归因分析。本文首发于 Aloudata 官方技术博客:《数据分析师如何能不依赖 IT,自助完成任意维度的下钻分析?》 转载请注明出处。 摘要:本文探讨…

2026年 吸塑品牌实力推荐榜:专业厂家深度解析,涵盖厚片吸塑、精密吸塑、大型吸塑制品的优质品牌全景测评

2026年吸塑品牌实力推荐榜:专业厂家深度解析,涵盖厚片吸塑、精密吸塑、大型吸塑制品的优质品牌全景测评 在当今制造业持续升级与产品个性化需求日益增长的背景下,塑料吸塑成型技术凭借其高效、灵活、成本可控的优势…