数据结构:红黑树的插入实现(C++)

在这里插入图片描述

个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》《C++》《Linux》

文章目录

  • 一、红黑树
  • 二、红黑树的插入
  • 三、代码实现
  • 总结


一、红黑树

红黑树的概念:
红黑树是一颗二叉搜索树,但在每个节点上增加一个存储位表示节点的颜色,该节点颜色不是红色就是黑色。通过对每一条从根节点到叶子结点路径上各个节点颜色的控制,确保没有一条路径会比其它路径长出两倍,因此红黑树是接近平衡。

在这里插入图片描述

那红黑树是通过哪些规则来对节点颜色进行控制?

  1. 每个节点不是红色就是黑色

  2. 根节点是黑色

  3. 如果一个节点是红色,则其两个子节点是黑色(不能有连续的红色节点)

  4. 对于每个节点,从该节点到其所以叶子结点的路径上,其黑色节点的数目相同

  5. 每个叶子结点都是黑色的(此处的叶子结点是空节点(NIL),方便我们计算路径的个数)
    注意:上述中的路径是从某一节点到NIL节点。如上图8节点到叶子结点就有5条路径,每条路径都有一个黑色节点。

那为什么遵循这5条规则,红黑树就能保证其最长路径中节点的个数不会超过最短路径节点个数的两倍?
我们假设从根节点到叶子结点的黑色节点数为n,那么最短路径不就是连续的黑色节点,最短路径的节点数为n,那么最长路径不就是红黑相间,最长路径的节点数为2n。所以红黑树保证其最长路径中节点的个数不会超过最短路径节点个数的两倍。


下面是红黑树节点的定义。

enum
{RED,BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode(T data = T()):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;T _data;int _col;
};

该定义中,我们默认将新节点颜色定义为红色,这样我们插入节点时需要维护规则的成本就少。如我们新插入一个红色节点,那么有可能会违背规则3(当其父节点是红色时,有连续红色节点),这时我们可能需要一些变色和旋转,来维持规则,但如果我们插入节点是黑色,那么我们一定违背4(每条路径上黑色节点数相同),这时我们可能需要对整个数进行操作。

二、红黑树的插入

红黑树也是一个二叉搜索树,插入新节点与二叉搜索树的操作一样,如果新插入节点比该节点大,新插入节点就去该节点的右子树,反之去该节点的左子树。

if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur != nullptr){parent = cur;if (cur->_data > data){cur = cur->_left;}else if(cur->_data < data){cur = cur->_right;}else // cur->_data == data{return false;}}cur = new Node(data);cur->_parent = parent;if (cur->_data > parent->_data){parent->_right = cur;}else{parent->_left = cur;}

这里我们主要分析新插入节点后,红黑树的情况和处理。对于旋转,这里并不会详细解析。对于旋转的详细解析在我的AVL树一文中。数据结构:AVLTree的插入和删除的实现

在分析情况前,我们先了解几个节点的含义,方便后续的理解。
在这里插入图片描述
接下来的所有情况都与这四个节点有关。

因为我们新插入的节点是红色,如果新插入节点的父节点是黑色,那么红黑树的规则未被破坏。如果新插入节点的父节点是红色,那么就有连续的红色节点。这是我们就要分情况讨论。

情况1. cur为红色,parent为红色,grandfather为黑色,uncle为红色
也就是如下图所示:
在这里插入图片描述
这种情况是最简单的情况,我们只需要将parent 与 uncle的颜色变成黑色(解决连续红色节点),再将grandfather的颜色变成红色(防止经过grandfather路径的黑色节点数+1)
在这里插入图片描述
但就如上图所示,因为我们将grandfather的颜色变成红色,如果grandfather的父节点的颜色也是红色,这时我们依旧有连续的红色节点,我们仍需对grandfather进行处理。
在这里插入图片描述
我们重复变色过程
在这里插入图片描述
这时,grandfather没有父节点,就可以停止了,但此时grandfather作为根节点为红色,我们需要特殊处理一下即可。这样对于该插入新节点的情况一就完成了。
下面是总结的模型:
在这里插入图片描述
对于这种cur,parent,uncle为红色,grandfather为黑色的情况,我们只需让parent,uncle变成红色,grandfather变成黑色,接着需要检查grandfather的父节点是否是红色,如果是红色,重复上述操作。如果是黑色,就可以结束了。

情况2:cur为红色,parent为红色,grandfather为黑色,uncle不存在或uncle存在且为黑色
在这里插入图片描述
这时情况1 的处理就行不通了,因为uncle要么不存在,要么本身就为黑色,如果将grandfather变成红色,那么经过grandfather的路径的黑色节点数就-1。所以我们要采取旋转+变色。
在这里插入图片描述
因为cur在parent的右侧,parent在grandfather的右侧,成直线。所以我们对grandfather左单旋,接着将parent的颜色变成黑色,grandfather的颜色变成红色(防止经过grandfather的路径的黑色节点数+1),又因为parent作为新的根节点为黑色,所以我们不需要去检查parent的父节点的颜色。(虽然我们也可以只将cur变为黑色,但这样我们就需要检查parent父节点的颜色)
那如果我们新增5节点要怎么处理?
在这里插入图片描述
此时我们也需要旋转+变色,但我们要双旋。
在这里插入图片描述
如上图,我们要先对parent左单旋,使grandfather,cur,parent在同一侧,接着使grandfather左单旋,cur变为黑色,grandfather变成红色。
如果parent在grandfather的左侧,情况与上述情况类似,只需要改变旋转方向即可。
下面是总结的模型:
单旋在这里插入图片描述
双旋
在这里插入图片描述

while (parent != nullptr && parent->_col != BLACK){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle != nullptr && uncle->_col == RED) // uncle存在且为红色{parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else if (uncle == nullptr || uncle->_col == BLACK) // uncle不存在 或 umcle存在且为黑{if (cur == parent->_left) // 同方向{RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else // cur == parent->_right 不同方向{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else // parent == grandfather->_right{Node* uncle = grandfather->_left;if (uncle != nullptr && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else if (uncle == nullptr || uncle->_col == BLACK){if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK; //特殊处理,保证根节点是黑色

则此,红黑树的插入就完成了。

三、代码实现

RBTree.h 文件是红黑树插入的实现
test.cpp 文件是测试用例

// RBTree.h 文件
#pragma onceenum
{RED,BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode(T data = T()):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;T _data;int _col;
};template<class T>
class RBTree
{typedef RBTreeNode<T> Node;
public:RBTree():_root(nullptr){}bool insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur != nullptr){parent = cur;if (cur->_data > data){cur = cur->_left;}else if(cur->_data < data){cur = cur->_right;}else // cur->_data == data{return false;}}cur = new Node(data);cur->_parent = parent;if (cur->_data > parent->_data){parent->_right = cur;}else{parent->_left = cur;}while (parent != nullptr && parent->_col != BLACK){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle != nullptr && uncle->_col == RED) // uncle存在且为红色{parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else if (uncle == nullptr || uncle->_col == BLACK) // uncle不存在 或 umcle存在且为黑{if (cur == parent->_left) // 同方向{RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else // cur == parent->_right 不同方向{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else // parent == grandfather->_right{Node* uncle = grandfather->_left;if (uncle != nullptr && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else if (uncle == nullptr || uncle->_col == BLACK){if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}// 读取红黑树最左侧节点Node* leftMost(){if (_root == nullptr){return nullptr;}Node* cur = _root;while (cur->_left != nullptr){cur = cur->_left;}return cur;}// 读取红黑树最右侧节点Node* rightMost(){if (_root == nullptr){return nullptr;}Node* cur = _root;while (cur->_right != nullptr){cur = cur->_right;}return cur;}bool isValidRBTree(){if (_root->_col == RED){return false;}// 找最左边的黑色节点数作为标准比较int pathBlack = 0;Node* cur = _root;while (cur != nullptr){if (cur->_col == BLACK){pathBlack++;}cur = cur->_left;}return _isValidRBTree(_root, 0, pathBlack);}bool _isValidRBTree(Node* root, int blackCount, int pathBlack){if (root == nullptr){if (blackCount == pathBlack)return true;elsereturn false;}if (root->_col == RED&& root->_parent != nullptr&& root->_parent->_col == RED){cout << "有连续的红色节点" << endl;return false;}if (root->_col == BLACK){blackCount++;}return _isValidRBTree(root->_left, blackCount, pathBlack)&& _isValidRBTree(root->_right, blackCount, pathBlack);}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;parent->_left = subLR;subL->_right = parent;parent->_parent = subL;if (subLR != nullptr){subLR->_parent = parent;}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;Node* pparent = parent->_parent;parent->_right = subRL;subR->_left = parent;parent->_parent = subR;if (subRL != nullptr){subRL->_parent = parent;}if (pparent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}subR->_parent = pparent;}}private:Node* _root;
};

//test.cpp 文件
#include <iostream>
#include <vector>using namespace std;
#include "RBTree.h"#define N 10000000void test1()
{vector<int> nums(N);srand((unsigned int)time(0));for (int i = 0; i < N; i++){nums[i] = rand() + i;//cout << nums[i] << endl;}RBTree<int> tree;for (auto val : nums){if (val == 11478){int i = 0;i++;}tree.insert(val);//cout << val << "->" << tree.isValidRBTree() << endl;}cout << tree.isValidRBTree() << endl;
}int main()
{test1();return 0;
}

总结

以上就是我对于红黑树插入实现的总结。感谢支持!!!
在这里插入图片描述

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

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

相关文章

redis三种集群方式

redis有三种集群方式&#xff1a;主从复制&#xff0c;哨兵模式和集群。 1.主从复制 主从复制原理&#xff1a; 从服务器连接主服务器&#xff0c;发送SYNC命令&#xff1b; 主服务器接收到SYNC命名后&#xff0c;开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所…

【Java 进阶篇】JQuery 事件绑定之事件切换:让页面动起来

欢迎来到这个充满动感的 JQuery 事件绑定之旅&#xff01;在这篇博客中&#xff0c;我们将深入研究 JQuery 中的事件切换&#xff0c;让你的页面焕发出活力和互动。无论你是前端小白还是有一定经验的开发者&#xff0c;相信这篇文章都会对你有所帮助。 走进事件切换的奇妙世界…

【NI-DAQmx入门】校准

1.设备定期校准的理由 随着时间的推移电子器件的特性会发生自然漂移&#xff0c;可能会导致测量结果的不准确性。防止出现良品和差品筛选出错的情况满足行业国际标准降低设备出现故障的风险使测量结果更具备参考性 2.查找NI设备的校准间隔。 定期校准会使DAQ设备的精度保持在…

电路的基本原理

文章目录 一、算数逻辑单元(ALU)1、功能2、组成 二、电路基本知识1、逻辑运算2、复合逻辑 三、加法器实现1、一位加法器2、串行加法器3、并行加法器 一、算数逻辑单元(ALU) 1、功能 算术运算&#xff1a;加、减、乘、除等 逻辑运算&#xff1a;与、或、非、异或等 辅助功能&am…

设计模式常见面试题

简单梳理下二十三种设计模式&#xff0c;在使用设计模式的时候&#xff0c;不仅要对其分类了然于胸&#xff0c;还要了解每个设计模式的应用场景、设计与实现&#xff0c;以及其优缺点。同时&#xff0c;还要能区分功能相近的设计模式&#xff0c;避免出现误用的情况。 什么是…

Linux 零拷贝splice函数

Linux splice 函数简介 splice 是 Linux 系统中用于在两个文件描述符之间移动数据的系统调用。它的主要作用是在两个文件描述符之间传输数据&#xff0c;而无需在用户空间进行数据拷贝。也是零拷贝操作. 函数原型 #include <fcntl.h> ssize_t splice(int fd_in, loff_…

【Go入门】 Go搭建一个Web服务器

【Go入门】 Go搭建一个Web服务器 前面小节已经介绍了Web是基于http协议的一个服务&#xff0c;Go语言里面提供了一个完善的net/http包&#xff0c;通过http包可以很方便的搭建起来一个可以运行的Web服务。同时使用这个包能很简单地对Web的路由&#xff0c;静态文件&#xff0c…

mac控制台命令小技巧

shigen日更文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 hello伙伴们&#xff0c;作为忠实的mac骨灰级别的粉丝&#xff0c;它真的给我带来了很多效率上的提升。那作为接…

python-opencv 培训课程笔记(2)

python-opencv 培训课程笔记&#xff08;2&#xff09; 1.图像格式转换 先看一下cvtColor函数的例子 #默认加载彩图 pathrD:\learn\photo\cv\cat.jpg# imread(path,way) #way0 灰度图。way1 彩图 #默认彩图 imgcv2.imread(path) img_dogcv2.imread(path_dog) #图片格式的转化…

Linux远程工具专家推荐(二)

8. Apache Guacamole Apache Guacamole 是一款免费开源的无客户端远程桌面网关&#xff0c;支持 VNC、RDP 和 SSH 等标准协议。无需插件或客户端软件&#xff1b;只需使用 HTML5 Web 应用程序&#xff08;例如 Web 浏览器&#xff09;即可。 这意味着您的计算机的使用不受任何一…

mysql练习1

-- 1.查询出部门编号为BM01的所有员工 SELECT* FROMemp e WHEREe.deptno BM01; -- 2.所有销售人员的姓名、编号和部门编号。 SELECTe.empname,e.empno,e.deptno FROMemp e WHEREe.empstation "销售人员";-- 3.找出奖金高于工资的员工。 SELECT* FROMemp2 WHE…

力扣刷题篇之位运算

系列文章目录 目录 系列文章目录 前言 一、位运算的基本运算 二、位运算的技巧 三、布隆过滤器 总结 前言 本系列是个人力扣刷题汇总&#xff0c;本文是数与位。刷题顺序按照[力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 - 力扣&#xff08;LeetCode&#xff0…