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

红黑树与红黑树的插入——用C++实现

一、红黑树简介

        红黑树是二叉搜索树的一种,区别于二叉平衡树,红黑树的平衡并不以平衡因子为依据进行平衡的调整而是以五条规则对红黑树进行定义,从而达成树的最长路径最多是树的最短路径的两倍长。以下是红黑树的五条规则:

  1. 节点非红即黑:每个节点只能是红色或黑色。

  2. 根节点必黑:树的根节点永远是黑色的。

  3. 叶子节点(NULL)为黑:所有叶子节点(空指针,通常用统一的哨兵节点 NULL 表示)都是黑色的。

  4. 红节点之子必黑:如果一个节点是红色的,那么它的两个子节点都必须是黑色的(即不能有两个连续的红色节点)。

  5. 黑高一致:从任意一个节点出发,到达其所有后代叶子节点(NULL)的路径上,包含的黑色节点数量(称为黑高)必须完全相同

        由于第五条,路径上黑色结点数量相同,在加上第四条,不能有两个连续的红色结点,就可以形成红黑树最长路径最多是树的最短路径的两倍长。

        那么红黑树与二叉平衡树相比有什么区别呢?

  • 首先,红黑树由于定义使得左右子树之间的高度差要求没有二叉平衡树那么严格,因此在插入删除时,需要进行旋转的操作会更少一点。因此,在这方面会比二叉平衡树更优。
  • 其次,就是红黑树的查找效率,对比平衡二叉树会稍差一点,由于左右子树之间的差距相比二叉平衡树变大,树高会更高一些。

综合以上两点,红黑树是在牺牲一部分查找效率的情况下,提高的插入和删除效率。且由于红黑树的特征,使红黑树与二叉平衡树之间的查找效率差距并不大,因此,日常使用更多的还是红黑树。

二、红黑树的插入

        对于红黑树的插入有一个可以直观演示的网站,有兴趣可以看看Red/Black Tree Visualization

2.1 如何维护红黑树的规则

        首先是第一条,结点只有红色或者黑色,只需要在结点中增加一个变量,用来记录颜色即可。

        第二条,根结点一定为黑,这就要求我们在调整完插入结点后,注意根结点的颜色是否为黑色。

        第三条,可以用来帮助检查是否为红黑树:记录路径上黑色结点的个数,检查到nullptr,也就是叶子结点时停止计数,即为一条路径上黑色结点的个数。

        第四条,不存在连续的红色结点,因此,在插入时,如果遇到了插入结点和父结点均为红色的情况,需要进行颜色的改变,甚至旋转的操作,使得第四条规则得到维护,具体情况,后面再分析。

        第五条,黑高一致,就需要在为了保证第四条进行改颜色和旋转操作的同时,注意不能改变黑高。同时,这也决定了新插入的结点的颜色,必须为红色,如果新插入的结点是黑色,那么就会影响其他路径,进行维护时代价过大。

2.2 如何维护红红不相邻与黑高一致

        若是新插入结点的父结点为黑色,则不需要进行调整。

        由于根结点必定为黑,因此,如果遇到了红红相邻的情况,说明插入的结点的父结点一定不是根结点,因此父结点一定存在父结点,也就是父结点的父结点。

        而维护的方法,也被总结为,看叔叔结点的颜色,叔叔结点总共有三种情况:不存在、红色、黑色。

2.2.1 叔叔结点为红色

        这个图仅抽象出了一种情况,不论插入结点在父结点的左右,或者父结点在祖父结点的左右,只要叔叔结点是红色,都只需要,将父结点和叔叔结点变为黑色,将祖父结点变为红色即可。

        这样在祖父结点这一棵子树上,不仅能维护第四条,也能维护第五条。但是,调整并不会在这里结束。

        由于祖父结点变为了红色,可能存在祖父结点的父结点为红色的情况,因此,要将祖父结点当成新插入的结点,继续向上调整。 

2.2.2 叔叔结点为黑色

        那么此时,说明结点一定是通过调整以后变成红色,而不是新插入的结点,那么其子树一定存在黑色结点。

        如果父结点是祖父结点的左孩子,当前结点是父结点的左孩子,则右旋,然后修改父结点和祖父结点的颜色。

        如果父结点是祖父结点的右孩子,当前结点是父结点的右孩子,则左旋,然后修改父结点和祖父结点的颜色。

        但是,双旋的情况与单旋不一样,双旋需要修改的是当前结点与祖父结点的颜色。

        综上,根据父结点在祖父结点的子树位置,和当前结点在父结点子树位置,确定是单旋还是双旋。再根据是单旋还是双旋,确定修改颜色的结点。

2.2.3 叔叔结点不存在

        叔叔结点不存在,实质上情况与叔叔结点为黑色的处理方式是一样的,也就是——根据父结点在祖父结点的子树位置,和当前结点在父结点子树位置,确定是单旋还是双旋。再根据是单旋还是双旋,确定修改颜色的结点。

        经过旋转和改色,原先祖父结点位置的颜色仍然是黑色,因此不再需要向上调整,调整结束。

        综上,叔叔结点为红色,则修改父结点、叔叔结点、祖父结点的颜色,然后以祖父结点为当前结点继续向上调整;叔叔结点为黑色,则进行旋转和改色,完成后不再需要向上调整。

以下为插入的代码实现,附带验证函数:

// 枚举值表示颜色
enum Colour
{RED,BLACK
};// 这里我们默认按key/value结构实现
template<class K, class V>
struct RBTreeNode
{// 这里更新控制平衡也要加入parent指针pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col;RBTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED){}
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv) {if (_root == nullptr) {_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;while (cur) {parent = cur;if (cur->_kv.first < kv.first) {cur = cur->_right;}else if (cur->_kv.first > kv.first) {cur = cur->_left;}else {return false;}}cur = new Node(kv);cur->_parent = parent;if (parent->_kv.first > kv.first) {parent->_left = cur;}else {parent->_right = cur;}if (parent->_col == BLACK) {return true;}Node* pp = parent->_parent;while (parent && pp) {Node* uncle = nullptr;if (parent == pp->_left) {uncle = pp->_right;}else {uncle = pp->_left;}if (uncle != nullptr && uncle->_col == RED) {parent->_col = BLACK;pp->_col = RED;uncle->_col = BLACK;cur = pp;if (cur->_parent == nullptr) {cur->_col = BLACK;return true;}if (cur->_parent->_col == BLACK) return true;parent = cur->_parent;pp = parent->_parent;}else {if (parent == pp->_left && cur == parent->_left) {RotateR(pp);parent->_col = BLACK;pp->_col = RED;}else if (parent == pp->_left && cur == parent->_right) {RotateLR(pp);cur->_col = BLACK;pp->_col = RED;}else if (parent == pp->_right && cur == parent->_left) {RotateRL(pp);cur->_col = BLACK;pp->_col = RED;}else {RotateL(pp);parent->_col = BLACK;pp->_col = RED;}break;}}while (parent != nullptr && parent->_parent != nullptr) {parent = parent->_parent;}_root = parent;return true;}bool IsBalanceTree(){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);}void InOrder() {_InOrder(_root);}private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}// 前序递归遍历bool Check(Node* root, int blackNum, const int refNum){if (root == nullptr){// 前序遍历走到空时,意味着一条路径走完了//cout << blackNum << endl;if (refNum != blackNum){cout << "存在黑色结点的数量不相等的路径" << endl;return false;}return true;}// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了if (root->_col == RED && root->_parent && 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 RotateR(Node* pParent) {Node* cur = pParent->_left;pParent->_left = cur->_right;cur->_right = pParent;if (pParent->_left != nullptr) {pParent->_left->_parent = pParent;}cur->_parent = pParent->_parent;pParent->_parent = cur;if (cur->_parent != nullptr) {if (cur->_parent->_left == pParent) {cur->_parent->_left = cur;}else {cur->_parent->_right = cur;}}}// 左单旋void RotateL(Node* pParent) {Node* cur = pParent->_right;pParent->_right = cur->_left;cur->_left = pParent;if (pParent->_right != nullptr) {pParent->_right->_parent = pParent;}cur->_parent = pParent->_parent;pParent->_parent = cur;if (cur->_parent != nullptr) {if (cur->_parent->_left == pParent) {cur->_parent->_left = cur;}else {cur->_parent->_right = cur;}}}// 右左双旋void RotateRL(Node* pParent) {Node* cur = pParent->_right;Node* left = cur->_left;RotateR(cur);RotateL(pParent);}// 左右双旋void RotateLR(Node* pParent) {Node* cur = pParent->_left;Node* right = cur->_right;RotateL(cur);RotateR(pParent);}Node* _root = nullptr;
};

相关文章:

  • 查询去重使用 DISTINCT 的性能分析
  • 图神经网络原理及应用简介
  • VS2022下C++ Boost库安装与使用使用
  • 血管的三维重建
  • 【Java】mybatis-plus乐观锁与Spring重试机制
  • 【Typst】5.文档结构元素与函数
  • 【计算机网络 第8版】谢希仁编著 第六章应用层 题型总结1 编码
  • JavaScript 递归构建树形结构详解
  • 闲谈PMIC和SBC
  • Message=“HalconDotNet.HHandleBase”的类型初始值设定项引发异常
  • v4l2常见操作-查看当前摄像头信息,帧率,控制参数,分辨率,支持格式,抓图实践等
  • 【2025年B卷】OD-100分-斗地主之顺子
  • 【大模型:知识图谱】--3.py2neo连接图数据库neo4j
  • 6月2日day43打卡
  • 预警功能深度测评:系统如何降低设备突发故障率?
  • 网络攻防技术九:网络监听技术
  • 每天总结一个html标签——area与map标签
  • 机器人开发前景洞察:现状、机遇、挑战与未来走向
  • NX890NX894美光固态闪存NX906NX908
  • DSN(数字交换网络)由什么组成?
  • 汕头网站制作网页/职业技能培训机构
  • 中国传统色彩网站建设方案/商城网站建设
  • 自己电脑做网站访问速度/做个电商平台要多少钱
  • 手机如何做微电影网站/自己建网站怎么建
  • html网页设计工具/开鲁网站seo
  • 常州互联网公司/深圳seo排名哪家好