数据结构:红黑树的插入实现(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.mzph.cn/news/148363.shtml

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

相关文章

深入理解栈与队列:从基本概念到高级实现

&#x1f493; 博客主页&#xff1a;江池俊的博客⏩ 收录专栏&#xff1a;数据结构探索&#x1f449;专栏推荐&#xff1a;✅cpolar ✅C语言进阶之路&#x1f4bb;代码仓库&#xff1a;江池俊的代码仓库&#x1f525;编译环境&#xff1a;Visual Studio 2022&#x1f389;欢迎大…

golang中的并发模型

并发模型 传统的编程语言&#xff08;如C、Java、Python等&#xff09;并非为并发而生的&#xff0c;因此它们面对并发的逻辑多是基于操作系统的线程。其并发的执行单元&#xff08;线程&#xff09;之间的通信利用的也是操作系统提供的线程或进程间通信的原语&#xff0c;比如…

【Unity】单例模式及游戏声音管理类应用

【Unity】单例模式及游戏声音管理类应用 描述 在日常游戏项目开发中&#xff0c;单例模式是一种常用的设计模式&#xff0c;它允许在应用程序的生命周期中只创建一个对象实例&#xff0c;并提供对该实例的全局访问点。通过使用单例模式&#xff0c;可以提高代码的可维护性和可…

2024年山东省职业院校技能大赛中职组 “网络安全”赛项竞赛试题-B卷

2024年山东省职业院校技能大赛中职组 “网络安全”赛项竞赛试题-B卷 2024年山东省职业院校技能大赛中职组 “网络安全”赛项竞赛试题-B卷A模块基础设施设置/安全加固&#xff08;200分&#xff09;A-1&#xff1a;登录安全加固&#xff08;Windows, Linux&#xff09;A-2&#…

Verilog基础:仿真时x信号的产生和x信号对于各运算符的特性

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 信号爆x也许是所有IC人的噩梦&#xff0c;满屏的红色波形常让人头疼不已&#xff0c;但x信号的产生原因却常常只有几种&#xff0c;只要遵循一定的代码规范&#…

超聚变服务器关闭超线程CPU的步骤(完整版)

前言: 笨鸟先飞&#xff0c;好记性不如烂笔头。 我们项目都用不到超线程CPU&#xff0c;所以调测设备的时候都需要关掉&#xff0c;最近新设备换成了超聚变的服务器&#xff0c;这篇记录我关闭&#xff08;超聚变&#xff09;服务器超线程CPU的方法步骤。 关闭超线程CPU的步骤…

JS-项目实战-鼠标悬浮变手势(鼠标放单价上生效)

1、鼠标悬浮和离开事件.js //当页面加载完成后执行后面的匿名函数 window.onload function () {//get:获取 Element:元素 By:通过...方式//getElementById()根据id值获取某元素let fruitTbl document.getElementById("fruit_tbl");//table.rows:获取这个表格…

基于单片机音乐弹奏播放DS1302万年历显示及源程序

一、系统方案 1、本设计采用51单片机作为主控器。 2、DS1302计时显示年月日时分秒。 3、按键可以弹奏以及播放音乐&#xff0c;内置16首音乐。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 /时钟显示**/ void init_1602_ds1302() { write…

机器学习-搜索技术:从技术发展到应用实战的全面指南

在本文中&#xff0c;我们全面探讨了人工智能中搜索技术的发展&#xff0c;从基础算法如DFS和BFS&#xff0c;到高级搜索技术如CSP和优化问题的解决方案&#xff0c;进而探索了机器学习与搜索的融合&#xff0c;最后展望了未来的趋势和挑战&#xff0c;提供了对AI搜索技术深刻的…

Lesson 04 模板入门

C&#xff1a;渴望力量吗&#xff0c;少年&#xff1f; 文章目录 一、泛型编程1. 引入2. 函数模板&#xff08;1&#xff09;函数模板概念&#xff08;2&#xff09;函数模板格式&#xff08;3&#xff09;函数模板的原理&#xff08;4&#xff09;函数模板的实例化&#xff08…

uniapp优化h5项目-摇树优化,gzip压缩和删除console.log

1.摇树优化 勾选摇树优化,打包删除死代码 2.gzip压缩和删除console.log 安装插件webpack和compression-webpack-plugin webpack插件 npm install webpack4.46.0 --save-devcompression-webpack-plugin插件 npm install compression-webpack-plugin6.1.1 --save-devconst Com…

PMCW体制雷达系列文章(4) – PMCW雷达之抗干扰

说明 本文作为PMCW体制雷达系列文章之一&#xff0c;主要聊聊FMCW&PMCW两种体制雷达的干扰问题。事实上不管是通信领域还是雷达领域&#xff0c;对于一切以电磁波作为媒介的信息传递活动&#xff0c;干扰是无处不在的。近年来&#xff0c;随着雷达装车率的提高&#xff0c;…

HTTP Error 500.31 - Failed to load ASP.NET Core runtime

在winserver服务器上部署net6应用后&#xff0c;访问接口得到以下提示&#xff1a; 原因是因为没有安装net6的运行时和环境&#xff0c;我们可以在windows自带的 “事件查看器” 查看原因。 可以直接根据给出的地址去官网下载sdk环境&#xff0c;安装即可 下载对应的net版本…

CentOS Linux release 7.9.2009 (Core)中安装配置Tomcat

一、安装JDK 部分内容可以参考我这篇文章&#xff1a;Windows11与CentOS7下配置与检测JDK与Maven环境变量 中的 2.2 安装jdk-8u371-linux-x64.tar.gz和配置环境变量/etc/profile //1、安装redhat-lsb yum install -y redhat-lsb//2、查看系统版本信息 lsb_release -a //3、查…

【C++入门】拷贝构造运算符重载

目录 1. 拷贝构造函数 1.1 概念 1.2 特征 1.3 常用场景 2. 赋值运算符重载 2.1 运算符重载 2.2 特征 2.3 赋值运算符 前言 拷贝构造和运算符重载是面向对象编程中至关重要的部分&#xff0c;它们C编程中的一个核心领域&#xff0c;本期我详细的介绍拷贝构造和运算符重载。 1. …

Transformer中WordPiece/BPE等不同编码方式详解以及优缺点

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

FPGA设计时序约束八、others类约束之Set_Case_Analysis

目录 一、序言 二、Set Case Analysis 2.1 基本概念 2.2 设置界面 2.3 命令语法 2.4 命令示例 三、工程示例 四、参考资料 一、序言 在Vivado的时序约束窗口中&#xff0c;存在一类特殊的约束&#xff0c;划分在others目录下&#xff0c;可用于设置忽略或修改默认的时序…

uniapp 微信小程序登录 新手专用 引入即可

预览 第一步导入插件 在引入的页面的登录按钮下拷贝一下代码 <template><view class"content"><button type"primary" click"login">微信登录</button></view><TC-WXlogin :wxloginwxlogin /> </templ…

05-Spring Boot工程中简化开发的方式Lombok和dev-tools

简化开发的方式Lombok和dev-tools Lombok常用注解 Lombok用标签方式代替构造器、getter/setter、toString()等重复代码, 在程序编译的时候自动生成这些代码 注解名功能NoArgsConstructor生成无参构造方法AllArgsConstructor生产含所有属性的有参构造方法,如果不希望含所有属…

[C/C++]数据结构 栈和队列()

一:栈 1.1 栈的概念及结构 栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作,进行数据插入和删除操作的一端称为栈顶,另一端称为栈底,栈中的数据元素遵守先进后出的原则. 压栈:栈的插入操作叫做进栈/压栈/入栈,将数据插入栈顶 出栈:栈的删除操作也叫出…