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

从零开始的C++学习生活 13:红黑树全面解析

个人主页:Yupureki-CSDN博客

C++专栏:C++_Yupureki的博客-CSDN博客

目录

前言

1. 红黑树的概念

1.1 红黑树的规则

1.2 红黑树如何保证平衡?

2. 红黑树的实现

3.1 基本结构

3.2 插入操作详解

3.2.1 变色

情况1:叔叔节点存在且为红色 (变色)

情况2:叔叔节点不存在或为黑色(旋转+变色)

单旋情况:

双旋情况:

3.3 查找操作

3.4 红黑树的验证


上一篇:从零开始的C++学习生活 12:AVL树全面解析-CSDN博客

前言

前面我们学习了AVL树,一种高级的二叉搜索树,通过平衡因子控制树的高低差不超过2来保持树的平衡,可以大幅增加查找数据的效率

而今天,我们又有一种由AVL树而来的变种:红黑树。同样是高级的二叉搜索树,红黑树同样限制了树的最大高度来增加效率,但是方法有所差异

我将深入探讨红黑树的原理、特性及实现细节,帮助你全面理解这一重要数据结构的工作原理。

1. 红黑树的概念

红黑树是一种特殊的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色(红色或黑色)。通过对从根到叶子的任何路径上的节点颜色施加约束,红黑树确保没有任何一条路径会比其他路径长出两倍,因而保持了近似平衡的状态。

1.1 红黑树的规则

  1. 颜色规则:每个节点不是红色就是黑色

  2. 根节点规则:根节点必须是黑色的

  3. 红色节点规则:红色节点的两个子节点必须是黑色的(即不能有连续的红色节点

  4. 黑色高度规则:从任意节点到其所有NULL节点的简单路径上,包含相同数量的黑色节点

总地来说,红黑树顾名思义,只有红和黑,没有蓝白树或者其他的。

当然这里红和黑只是一种标志,你要想变成其他的颜色其实也可以awa

回答我们所说的,通过上面的规则分析,可以知道不能由连续的红色节点,即父亲和孩子不能同时为红色,但兄弟就不用,毕竟都不是连续的。但是可以存在连续的黑色节点

要注意的是,所有路径上的黑色节点数量相同,这里的路径指的是从根节点到任意一个NULL节点,不是直观的只算有节点的路径。

这样能够保证红黑树的最小长度是全黑(N),最大长度是红和黑交叉出现(2*N),因此限制了红黑树的高度

1.2 红黑树如何保证平衡?

红黑树通过上述四条规则巧妙地维持了树的平衡:

  • 根据规则4,从根到NULL节点的每条路径都有相同数量的黑色节点。设最短路径(全黑路径)的黑色节点数为bh

  • 根据规则2和3,最长路径由黑红节点交替组成,其长度不超过2×bh

  • 因此,最长路径不会超过最短路径的两倍

假设红黑树有N个节点,最短路径长度为h,则有:
2^h - 1 < N < 2^(2×h) - 1

由此可得 h ≈ logN,这意味着红黑树的插入、删除和查找操作的最坏情况时间复杂度都是O(logN)。

与AVL树相比,红黑树对平衡的控制相对宽松,这使得在插入相同数量节点时,红黑树所需的旋转操作更少,整体性能更加稳定。

2. 红黑树的实现

3.1 基本结构

红黑树有红色和颜色,因此我们利用枚举常量

// 枚举值表示颜色
enum Colour {RED,BLACK
};

红黑树的节点和AVL树的节点相似,只不过多了颜色

// 红黑树节点
template<class K, class V>
struct RBTreeNode {std::pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col;RBTreeNode(const std::pair<K, V>& kv): _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED){}
};

红黑树本体也和AVL树极其相似

template<class K, class V>
class RBTree {typedef RBTreeNode<K, V> Node;
private:Node* _root = nullptr;// 其他成员函数...
};

3.2 插入操作详解

红黑树的插入我们默认按照二叉搜索树的规则

如果是非空树,插入的一定是红色节点,因为红黑树保证每条路径上的黑色节点相同。如果插入黑色节点,那么会破坏相等这一条件

如果是空树,插入的节点作为根节点,颜色设为黑色

之后要检查并修复红黑树性质:如果父节点是黑色,插入完成;如果父节点是红色,需要根据叔叔节点的颜色进行不同的处理

3.2.1 变色

假设我们插入的节点之前是红色节点,那么就会出现红红的情况,因此我们需要进行变色处理

这里我们把新插入的节点37作为孩子,40作为父亲,45作为祖父,48作为叔叔

情况1:叔叔节点存在且为红色 (变色)

在这里叔叔存在且为红色,那么我们就把parent和uncle变为黑色,grandparent变为红色

因为grandparent为红色之后,可能grandparent的parent也为红色,例如下图,因此我们还需向上检查,把child赋值为grandparent

情况2:叔叔节点不存在或为黑色(旋转+变色)
单旋情况:

对于上面的右图,叔叔虽然存在但是为黑色,那么我们还需要进行旋转处理

这里我们专门把child,parent,grandparent拆分出来(uncle无需关心,同时把偷偷把child变到左边去,child在右边是双旋情况)

我们对grandparent进行右单旋,同时把parent变为黑色,grandparent变为黑色

到了这种情况,uncle要么不存在要么为黑色,我们不用管uncle到底存不存在,也不用进行任何操作,继续保持grandparent指向uncle即可,别问,问就是巧妙awa,可以细品

我们再把child赋值为parent,由于parent已经为黑色了,不管parent和parent是红色还是黑色,都不会冲突,因此可以直接退出

双旋情况:

对于child在parent的右边情况,和AVL树类似,我们需要先对parent进行左单旋,保证三个节点在一条直线上,才能对grandparent进行右单旋

插入过程完成代码:

bool Insert(const K& key, const V& value)
{if (_root == nullptr){_root = new Node({key,value});_root->_col = BLACK;return true;}Node* newnode = new Node({ key,value });newnode->_col = RED;Node* cur = _root;Node* child = cur;while (cur)//按照二叉搜索树的规则插入{if (key < cur->_kv.first){child = cur;cur = cur->_left;}else if (key > cur->_kv.first){child = cur;cur = cur->_right;}else{find(key)->_kv.second++;return true;}}if (key < child->_kv.first){child->_left = newnode;newnode->_parent = child;}else{child->_right = newnode;newnode->_parent = child;}child = newnode;Node* parent = child->_parent;while (child->_col == RED && parent && parent->_col == RED)
//当child为红色时并且parent为黑色时继续循环{parent = child->_parent;Node* pparent = parent->_parent;Node* uncle = nullptr;if (pparent == nullptr)break;else{if (pparent->_left == parent)uncle = pparent->_right;elseuncle = pparent->_left;if (uncle && uncle->_col == RED)//叔叔存在并且为红色直接变色{uncle->_col = BLACK;parent->_col = BLACK;pparent->_col = RED;child = pparent;parent = child->_parent;}else//叔叔不存在或者为黑色,需要旋转和变色{if (pparent->_left == parent){if (parent->_right == child){RotateL(parent);RotateR(pparent);child->_col = BLACK;pparent->_col = RED;}else{RotateR(pparent);parent->_col = BLACK;pparent->_col = RED;child = parent;parent = child->_parent;}}else{if (parent->_left == child){RotateR(parent);RotateL(pparent);child->_col = BLACK;pparent->_col = RED;}else{RotateL(pparent);parent->_col = BLACK;pparent->_col = RED;child = parent;parent = child->_parent;}}}}}_root->_col = BLACK;//在变色处理时可能会对根节点进行变动,根节点需要保持黑色return true;
}

3.3 查找操作

红黑树的查找操作与普通二叉搜索树完全相同,时间复杂度为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;
}

3.4 红黑树的验证

验证红黑树是否满足所有规则是确保实现正确的关键。验证方法包括:

  1. 检查根节点是否为黑色

  2. 检查是否存在连续的红色节点

  3. 检查所有路径的黑色节点数量是否相同

我们利用递归来实现

规则1很好判断

规则2我们对每个节点和其父节点检查即可

规则3我们可以定义一个专门统计一条路径上黑色节点数量的变量,一直传给下一个节点,随后判断左右两条路是否相等即可

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);
}

http://www.dtcms.com/a/521043.html

相关文章:

  • RealVNC Viewer(Windows控制Mac)
  • mac下载wget
  • SQL进阶:深入解析SQL执行顺序
  • 专业网站建设的公司哪家好合肥计算机培训机构
  • C#实现摄像头视频录制与保存
  • 东莞网站建没可信网站是什么意思
  • led行业网站源码wordpress会员查看发布插件
  • 网站建设前期需要干嘛许昌网站建设哪家最好
  • 【Linux学习笔记】基于阻塞队列和环形队列的生产者消费者模型
  • GAN生成对抗网络学习-例子:生成逼真手写数字图
  • WPF MVVM下 ItemsControl条目命令绑定传参
  • 贵州网站制作公司电话wordpress有留言时邮件提醒
  • Python 脚本在工作日(周一到周五)的 8:00 到 19:00 之间持续运行,并在其他时间暂停(延时)
  • 婚庆网站大全深圳企业网站制作公司查询
  • 当城市有了“空间智能体”:一座长江首城的智慧蝶变
  • 机械类做的最好的网站网站开发代理江苏
  • 让别人做网站图片侵权网站简易后台
  • seo针对网站做策划大型网站开发合同
  • Macao资料生成程序,全新的UI 三端自适应PHP空间
  • 1Panel 安装与使用全指南:从部署到实战运维
  • Katalon Studio自愈测试功能
  • 非java、python、c/c++、perl、php、sql等的文章
  • 企业网站的建设与应用开题报告自己搭建app
  • 实验三:3-8线译码器设计
  • 深入浅出:马尔科夫链完全指南
  • 国外域名抢注网站seo顾问什么职位
  • 怎么做网站dns加速销售订单管理系统软件
  • DevOps工具链选型,Atlassian or TikLab哪一款更好用?
  • 网站实现搜索功能网站开发 平面设计
  • 河北建设厅官网站首页手机兼职有哪些