C++学习笔记:红黑树

红黑树

  • 什么是红黑树
  • 红黑树的规则
  • 红黑树节点的定义
  • 红黑树的插入
    • 空树插入
    • 非空插入条件判断
    • 新插入的节点 cur 不为 root 且 parent->_col 为红就需要调整
    • 父节点为左 grandf->left == parent
      • 当uncle节点为红色时,只需要进行颜色调整,即可
      • 当uncle为空 或 者存在但是为黑
        • parent 为left , cur 为 left 单纯的grandf右旋
          • uncle 为空
          • uncle不为空
        • parent 为left , cur 为 right 先左旋parent再右旋grandf
          • uncle为空
          • uncle不为空
    • 父节点为右 grandf->right == parent
  • 代码实现

什么是红黑树

红黑树是一种特殊的由二叉搜索树为底的数据结构,**之所以被成为红黑树,是因为在每个节点增加一个存储位表示这个节点的颜色,不是黑色就是红色,**因为其特数的规则,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。并具有更优的算法复杂度.
在这里插入图片描述

红黑树的规则

正是因为有以下规则的限制,红黑树才能是红黑树

  • 每个结点不是红色就是黑色
  • 根节点是黑色的
  • 红色节点的两个孩子结点是黑色的,即不存在两个连续的红色节点
  • 从任一节点到其每个叶子节点的所有简单路径都包含相同数目的黑色节点:这也称为黑色平衡,确保树的高度相对平衡,从而保证了最长路径不超过最短路径的两倍。
  • 叶子节点均为黑色

红黑树节点的定义

在定义节点之前,最好先定义一个枚举类型,用来记录每个节点的颜色
并且因为涉及到旋转调整,因此红黑树和AVL树一样需要记录父亲节点

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

红黑树的插入

红黑树的插入和AVL树的插入相似,但不同点是AVL树插入完成后改变的是平衡因子,而红黑树插入后改变的是节点的颜色.仅凭节点的旋转调整和节点颜色的改变就可以使之成为红黑树,不得不说发明红黑树的人真的是个天才!
因为涉及到后续迭代器的实现,因此我这里使用带头的红黑树来举例
在这里插入图片描述

我们将红黑树的插入分成几种情况依次来看

空树插入

当红黑树是空树时则直接插入,并且每次插入完成后将红黑树的根置为黑色,没什么特别的思路,直接上代码

		if (_root == nullptr){//根节点必须为黑色Node* root = new Node(data, BLACK);_root = root;_root->_parent = _pHead;_pHead->_parent = _root;}

非空插入条件判断

新插入的节点为红色,并且按照正常的二叉搜索树的条件去依次插入

			Node* cur = _root;//像搜索二叉树一样插入while (cur){if (cur->_data > data){parent = cur;cur = cur->_left;}else if (cur->_data < data){parent = cur;cur = cur->_right;}else{return false;}}//到位置了,创建节点并赋予红色 cur = new Node(data, RED);//判断是父节点的左还是右并进行链接if (data < parent->_data){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;
	链接完成后,查看父亲节点是否为红色  因为新节点为红色,若父节点也为红就需要进行调整

并且在每次插入完成后需要进行检查,若还是这种情况,则需要继续调整

新插入的节点 cur 不为 root 且 parent->_col 为红就需要调整

因为新插入的节点默认为红色,此时父亲节点也为红就需要进行调整

而红黑树的调整不仅仅只看cur节点和parent节点,还需要查看uncle(叔叔节点)和grandf(爷爷节点)节点及其颜色来进行判断
uncle节点:
在这里插入图片描述
接下来根据条件来进行调整

父节点为左 grandf->left == parent

当uncle节点为红色时,只需要进行颜色调整,即可

在这里插入图片描述
这种情况下这颗子树的黑色节点的高度依然没有发生变化
在进行颜色的调整后继续检查父亲节点

当uncle为空 或 者存在但是为黑

此时就要进行旋转和变色了,因为已经没有办法仅仅只通过变色来使这个子树的黑色节点的高度不发生变化了

在这里插入图片描述

parent 为left , cur 为 left 单纯的grandf右旋
uncle 为空

在这里插入图片描述

uncle不为空

此时当前的这个新插入的节点一定是从子树中调节变色变上来的,因为在有当前节点之前,parent一定有一个黑色节点的子树,否则这颗红黑树不平衡,
在这里插入图片描述

这种情况下这颗子树的黑色节点的高度依然没有发生变化
此时插入完成

parent 为left , cur 为 right 先左旋parent再右旋grandf
uncle为空

在这里插入图片描述

uncle不为空

在这里插入图片描述
在这里插入图片描述

父节点为右 grandf->right == parent

父节点为右时,与父节点为左完全相反,对照查看即可

代码实现

为了方便后续迭代器的实现,我使用了带头的红黑树:
定义头文件 RBTree.h

#pragma once#include<iostream>
using namespace std;enum Color
{RED,BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode* _parent;RBTreeNode* _left;RBTreeNode* _right;T _data;Color _col;RBTreeNode(const T& data = T(),Color color = RED):_parent(nullptr), _left(nullptr), _right(nullptr), _col(color), _data(data){}};template<class T>
class RBTree
{typedef RBTreeNode<T> Node;
public:RBTree(){_pHead = new Node();_pHead->_left = _pHead;_pHead->_right = _pHead;}// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false// 注意:为了简单起见,本次实现红黑树不存储重复性元素bool Insert(const T& data){Node*& _root = _pHead->_parent;if (_root == nullptr){//根节点必须为黑色Node* root = new Node(data, BLACK);_root = root;_root->_parent = _pHead;_pHead->_parent = _root;}else{Node* parent = nullptr;Node* cur = _root;//像搜索二叉树一样插入while (cur){if (cur->_data > data){parent = cur;cur = cur->_left;}else if (cur->_data < data){parent = cur;cur = cur->_right;}else{return false;}}//到位置了,创建节点并赋予红色 cur = new Node(data, RED);//判断是父节点的左还是右并进行链接if (data < parent->_data){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//链接完成后,查看父亲节点是否为红色  //因为新节点为红色,若父节点也为红就需要进行调整while (parent != _pHead && parent->_col == RED){Node* uncle = nullptr;Node* grandf = parent->_parent;if (grandf->_left == parent){//    g//   p    u//  c/cuncle = grandf->_right;//叔叔存在且为红if (uncle && uncle->_col == RED){//变色parent->_col = uncle->_col = BLACK;grandf->_col = RED;//继续往上更新cur = grandf;parent = cur->_parent;}//叔叔为空 或 者存在但是为黑else{//p 为left  cur 为 left  单纯的右旋if (cur == parent->_left){RotateR(grandf);parent->_col = BLACK;grandf->_col = RED;}//p 为left  cur 为 right   先左旋p再右旋grandfelse{RotateL(parent);RotateR(grandf);cur->_col = BLACK;grandf->_col = RED;}break;}}else //grandf->_right == parent{//    g//  u    p//      c/cuncle = grandf->_left;//叔叔存在且为红if (uncle && uncle->_col == RED){//变色parent->_col = uncle->_col = BLACK;grandf->_col = RED;//继续往上更新cur = grandf;parent = cur->_parent;}//叔叔不存在或者 叔叔为黑else{//p 为 right c 为 right 单纯的左旋if (cur == parent->_right){RotateL(grandf);parent->_col = BLACK;grandf->_col = RED;}//p 为 right c 为 left 先右旋parent再左旋grandfelse{RotateR(parent);RotateL(grandf);cur->_col = BLACK;grandf->_col = RED;}break;}}}}_root->_col = BLACK;_pHead->_left = LeftMost();_pHead->_right = RightMost();return true;}// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptrNode* Find(const T& data){Node*& _root = _pHead->_parent;Node* cur = _root;while (cur){if (cur->_data > data){cur = cur->_left;}else if (cur->_data < data){cur = cur->_right;}else{return cur;}}return nullptr;}// 获取红黑树最左侧节点Node* LeftMost(){if (_pHead->_parent == nullptr){return _pHead;}Node* root = _pHead->_parent;;while (root->_left){root = root->_left;}return root;}// 获取红黑树最右侧节点Node* RightMost(){if (_pHead->_parent == nullptr){return _pHead;}Node* root = _pHead->_parent;;while (root->_right){root = root->_right;}return root;}// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测bool IsValidRBTRee(){Node*& _root = _pHead->_parent;//空树if (_root == nullptr){return true;}else if(_root->_col == BLACK){size_t blacknum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){blacknum++;}cur = cur->_left;}size_t k = 0;return _IsValidRBTRee(_root, k , blacknum);}else{return false;}}void InOrder(){_InOrder(_pHead->_parent);cout << endl;}
private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_data << ":";if (root->_col == RED){cout << "red"<<endl;}else{cout << "black" << endl;}_InOrder(root->_right);}bool _IsValidRBTRee(Node* root, size_t k, size_t blacknum ){if (root == nullptr){if (k != blacknum){cout << "黑色节点平衡" << endl;return false;}return true;}if (root->_col == BLACK){k++;}Node* parent = root->_parent;if (parent && parent->_col == RED && parent != _pHead && root->_col == RED){cout << "有连续的红色节点" << endl;return false;}return _IsValidRBTRee(root->_left, k, blacknum)&& _IsValidRBTRee(root->_right, k, blacknum);}// 左单旋void RotateL(Node* parent){Node*& _root = _pHead->_parent;Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;Node* pparent = parent->_parent;parent->_parent = subR;if (subRL){subRL->_parent = parent;}if (parent == _root){_root = subR;subR->_parent = _pHead;}else{if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}subR->_parent = pparent;}}// 右单旋void RotateR(Node* parent){Node*& _root = _pHead->_parent;Node* pparent = parent->_parent;Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;parent->_parent = subL;subL->_right = parent;if (subLR){subLR->_parent = parent;}if (_root == parent){_root = subL;subL->_parent = _pHead;}else{if (pparent->_left == parent){pparent->_left = subL;}else{pparent->_right = subL;}subL->_parent = pparent;}}// 为了操作树简单起见:获取根节点Node* GetRoot(){return  _pHead->_parent;}private:Node* _pHead;
};

小小测试以下

#define _CRT_SECURE_NO_WARNINGS 1#include"RBTree.h"void test1()
{int arr[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };RBTree<int> rb;for (auto& e : arr){rb.Insert(e);}rb.InOrder();if (rb.Find(11) != nullptr){cout << "get find" << endl;}if (rb.IsValidRBTRee()){cout << "is RBTree" << endl;}else{cout << "is not BRTree" << endl;}
}int main()
{test1();return 0;
}

在这里插入图片描述

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

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

相关文章

【ICer的脚本练习】给模块顶层生成一个dummy文件 —— gen_dummy

系列的目录说明请见:ICer的脚本练习专栏介绍与全流程目录-CSDN博客 通过这篇文章,咱们来系统的看一下一个典型的python脚本应用和正则表达式的使用。 gen_dummy这个脚本的应用场景是这样的:在已经确定了顶层接口之后,模块的开发人员A开始模块编码。而此时上层的开发人员B需…

案例分析篇07:数据库设计相关28个考点(23~28)(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章推荐: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12601310.html 【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例分析篇-…

服务器主机云主机在日常维护需要注意的几个点

服务器的日常维护对于确保服务器的稳定运行和安全性非常重要&#xff0c;以下是一些常见的服务器日常维护方面&#xff1a; 定期更新操作系统和软件&#xff1a;确保服务器的操作系统、应用程序以及安全补丁都是最新的&#xff0c;以填补已知的安全漏洞和提高系统性能。监视服务…

【网络安全】-数字证书

数字证书 数字证书是互联网通讯中用于标志通讯各方身份信息的一串数字或数据&#xff0c;它为网络应用提供了一种验证通信实体身份的方式。具体来说&#xff0c;数字证书是由权威的证书授权&#xff08;CA&#xff09;中心签发的&#xff0c;包含公开密钥拥有者信息以及公开密…

Python 学习——Python BeautifulSoup 库文档

目录 一、 Beautiful Soup 4.4.0 文档1.1 寻求帮助 二、 快速开始三、 安装 Beautiful Soup3.1 安装完成后的问题3.2 安装解析器 四、 如何使用五、 对象的种类5.1 Tag5.1.1 Name5.1.2 Attributes5.1.3 多值属性 5.2 可以遍历的字符串5.3 BeautifulSoup5.4 注释及特殊字符串 六…

硬件工程师面试题梳理-百度硬件面试题

硬件工程师基本职责 在公司里面&#xff0c;硬件工程师的主要职责包括设计、开发和测试硬件系统&#xff0c;以满足产品需求和性能要求。他们负责确保硬件系统的可靠性、稳定性和可维护性&#xff0c;并与软件工程师和其他团队成员合作&#xff0c;以确保硬件和软件的协同工作…

有名信号量、网络协议模型、UDP编程发送端

我要成为嵌入式高手之3月5日Linux高编第十五天&#xff01;&#xff01; ______________________________________________________ 学习笔记 有名信号量 1、创建semget #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(…

伊芙丽签约实在智能,实在Agent数字员工助力品牌效能飙升

近日&#xff0c;国内知名时尚女装品牌伊芙丽与实在智能达成合作&#xff0c;引入业内领先的平台级自动化产品实在Agent数字员工——取数宝&#xff0c;自动获取天猫、淘宝、抖音等线上平台营销数据&#xff0c;开启全域化营销的“提效之旅”。 实在Agent智能体 伊芙丽集团成立…

java agent技术的注入利用与避坑点

什么是Java agent技术&#xff1f; Java代理&#xff08;Java agent&#xff09;是一种Java技术&#xff0c;它允许开发人员在运行时以某种方式修改或增强Java应用程序的行为。Java代理通过在Java虚拟机&#xff08;JVM&#xff09;启动时以"代理"&#xff08;agent…

CPU飙升和OOM排查思路

CPU使用率高和OOM&#xff08;Out of Memory&#xff09;异常是两个不同但可能相互关联的问题。下面分别对这两个问题的排查流程进行详细说明&#xff1a; CPU使用率高排查流程 实时监控与初步定位&#xff1a; 使用系统工具&#xff0c;如Linux中的top命令查看整体CPU使用情况…

分发平台如何支持热更

随着移动应用程序和游戏的迅猛发展&#xff0c;用户对于获得最新功能和修复bug的期望也越来越高。为了满足这一需求&#xff0c;现代的分发平台越来越注重在应用程序或游戏发布后能够支持热更新的功能。热更新是指通过网络直接获取更新并应用到用户设备上&#xff0c;而无需重新…

云原生周刊:Helm Charts 深入探究 | 2024.3.11

开源项目推荐 Glasskube Glasskube 提供了一个用于 Kubernetes 的缺失的包管理器。它具有图形用户界面(GUI)和命令行界面(CLI)。Glasskube 包是具备依赖感知、GitOps 准备和可以通过中央公共包仓库自动更新的特性。 imgpkg imgpkg&#xff08;发音为&#xff1a;"imag…

WPF 该线程是用不接受参数的 ThreadStart 委托创建的。

创建无参数线程是无法发去传递参数的&#xff0c;需要把 《 thread.Start(“张三”); 》改为《 thread.Start(); 》 把参数去掉就可以了。 public RegisterWindow(){InitializeComponent();//无参数线程Thread thread new Thread(pageLoad);thread.IsBackground true;//thr…

ubuntu 23开机界面美化教程

效果 方法 GRUB开机界面美化 从上述网站中&#xff0c;查找GRUB Themes分类&#xff0c;并下载GRUB主题包&#xff08;tar.gz格式&#xff09;&#xff0c;如CyberSynchro.tar.gz&#xff1b; 解压下载得到的压缩包&#xff0c;得到CyberSynchro&#xff1b; 将CyberSynchro…

leetcode 热题 100_轮转数组

题解一&#xff1a; 新数组存储&#xff1a;另外用一个数组存储移动后的结果&#xff0c;再复制回原数组。 class Solution {public void rotate(int[] nums, int k) {int[] result new int[nums.length];for (int i 0; i < nums.length; i) {result[(i k) % nums.lengt…

llama factory 是如何加载数据集 通过对数据集加载的代码的理解编写自定义数据集训练代码

第一层从训练代码追踪到以下代码 def get_dataset(tokenizer: "PreTrainedTokenizer",model_args: "ModelArguments",data_args: "DataArguments",training_args: "Seq2SeqTrainingArguments",stage: Literal["pt", "…

Python自动化测试之Python简介及环境安装配置

经过持续的"内卷"&#xff0c;编程变成测试工程师不可或缺的一项能力&#xff0c;掌握了一门编程语言,使你在面试过程中更有竞争力&#xff0c;是升值加薪的利器。 一、Python发展史 Python 是由 Guido van Rossum 在八十年代末和九十年代初&#xff0c;在荷兰国家数…

springboot同时接收json数据和 MultipartFile

首先测试接口发送方式。。。。。注意发送结构&#xff01; 后端接收RequestPart SaCheckPermission("system:records:add")Log(title "【用药纪录】", businessType BusinessType.INSERT)RepeatSubmit()PostMapping()public R<Void> add( RequestP…

大模型GPU监控之nvitop

背景 在进行大模型训练的时候&#xff0c;往往需要用到多张GPU卡&#xff0c;如何实现多卡的管理和监控&#xff0c;这是一个比较好的话题&#xff0c;下面介绍一个小工具。 安装nvitop pip install nvitop nvitop -m full 监控界面

抖音视频提取gif怎么做?分分钟帮你生成gif

通过将视频转换成gif动图的方式能够方便的在各种平台上分享、传播。相较于视频文件&#xff0c;gif动图的体积更小&#xff0c;传播起来更方便&#xff0c;能够吸引大众的注意力。下面&#xff0c;就来给大家分享一个gif图片制作&#xff08;https://www.gif.cn/&#xff09;的…