深入浅出C++ ——手撕红黑树

news/2024/4/26 18:46:04/文章来源:https://blog.csdn.net/weixin_55051736/article/details/127971072

文章目录

    • 一、红黑树的概念
    • 二、红黑树的性质
    • 三、红黑树节点的定义
    • 四、红黑树的插入操作
    • 五、红黑树的验证
    • 五、红黑树的删除
    • 六、红黑树与AVL树的比较
    • 七、红黑树的应用
    • 八、红黑树模拟实现

一、红黑树的概念

  红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

在这里插入图片描述


二、红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的,即树中没有连续的红色节点
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点,即每条路径的黑色节点数量相等
  5. 每个叶子结点(这里的叶子结点指的是空结点,也叫做NIL节点 )都是黑色的

  红黑树中第三和第四条规则构成互斥,极限的最短一定是全黑,极限的最长一定是一黑一红


三、红黑树节点的定义

enum Colour
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv){}pair<K, V> _kv;		//节点的值域Colour _col;		//节点的颜色RBTreeNode<K, V>* _left;	//节点的左孩子RBTreeNode<K, V>* _right;	//节点的右孩子RBTreeNode<K, V>* _parent;	//节点的双亲
};

四、红黑树的插入操作

  红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:一、按照二叉搜索的树规则插入新节点。二、检测新节点插入后,红黑树的性质是否造到破坏。

  因为新节点的默认颜色是红色,如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三(不能有连续的红色节点),此时需要对红黑树分情况来讨论。


情况一: cur为红,p为红,g为黑,u存在且为红

  cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点。此处看到的树,可能是一颗完整的树,但是也有可能是一颗子树。

在这里插入图片描述

  解决方式是将将p,u改为黑,g改为红。继续把g当成cur,g是根节点,需要将g改为黑色,如果g是子树且g的双亲颜色为红色,则需要继续向上调整。


情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

  如果u节点不存在,那么cur一定为新插入的节点,否则就违反性质4。如果u节点存在,则一定是黑色的,cur之前也是黑色的,因为新增节点是a、b节点,所以现在它是红色。
在这里插入图片描述

  解决方式:如果p为g的左孩子,cur为p的左孩子,则进行右单旋转;p为g的右孩子,cur为p的右孩子,则进行左单旋转。然后再将p变黑,g变红。


情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑
在这里插入图片描述
  解决方式:如果p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;如果p为g的右孩子,cur为p的左孩子,则针对p做右单旋转。旋转后就转变为了情况二。


总结

  在这三种情况中,都有cur为红,p为红,g为黑,可以看出红黑树的关键是叔叔。U存在且为红则变色继续往上处理,U不存在或为黑,则旋转变色

  情况二和情况三的主要区别是单旋和双旋的不同。从图中可以看出,当p g cur为一条直线的时候,也就是情况二,单旋即可;p g cur 为一条折线的时候,也就是情况三,需要双旋。


代码实现

bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur)	//找插入位置{if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);//新节点的默认颜色是红色,因为如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整cur->_col = RED;if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfater = parent->_parent;assert(grandfater);assert(grandfater->_col == BLACK);// 关键看叔叔if (parent == grandfater->_left){Node* uncle = grandfater->_right;// 情况一 : uncle存在且为红,变色+继续往上处理if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfater->_col = RED;// 继续往上处理cur = grandfater;parent = cur->_parent;}// 情况二+三:uncle不存在 + 存在且为黑else{// 情况二:右单旋+变色//     g //   p   u// cif (cur == parent->_left){RotateR(grandfater);parent->_col = BLACK;grandfater->_col = RED;}else{// 情况三:左右单旋+变色//     g //   p   u//     cRotateL(parent);RotateR(grandfater);cur->_col = BLACK;grandfater->_col = RED;}break;}}else // (parent == grandfater->_right){Node* uncle = grandfater->_left;// 情况一if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfater->_col = RED;// 继续往上处理cur = grandfater;parent = cur->_parent;}else{// 情况二:左单旋+变色//     g //   u   p//         cif (cur == parent->_right){RotateL(grandfater);parent->_col = BLACK;grandfater->_col = RED;}else{// 情况三:右左单旋+变色//     g //   u   p//     cRotateR(parent);RotateL(grandfater);cur->_col = BLACK;grandfater->_col = RED;}break;}}}_root->_col = BLACK;return true;
}

五、红黑树的验证

  红黑树的检测分为两步:一、检测其是否满足二叉搜索树,这里只需要中序遍历是否为有序序列即可。二、检测其是否满足红黑树的性质。

public:bool IsBalance(){if (_root == nullptr){return true;}if (_root->_col == RED){cout << "根节点不是黑色" << endl;return false;}int benchmark = 0;return PrevCheck(_root, 0, benchmark);}
private:bool PrevCheck(Node* root, int blackNum, int& benchmark){if (root == nullptr){if (benchmark == 0){benchmark = blackNum;return true;}if (blackNum != benchmark){cout << "某条黑色节点的数量不相等" << endl;return false;}else{return true;}}if (root->_col == BLACK){++blackNum;}if (root->_col == RED && root->_parent->_col == RED){cout << "存在连续的红色节点" << endl;return false;}return PrevCheck(root->_left, blackNum, benchmark)&& PrevCheck(root->_right, blackNum, benchmark);}

五、红黑树的删除

  红黑树的删除过于复杂,可以参考 红黑树的删除 这篇博客


六、红黑树与AVL树的比较

  红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log2Nlog_2 Nlog2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数所以在经常进行增删的结构中性能比AVL树更优而且红黑树实现比较简单,所以实际运用中红黑树更多


七、红黑树的应用

  红黑树的应用场景建议观看这个视频讲解红黑树在linux中的3种应用场景

八、红黑树模拟实现

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;enum Colour
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv){}pair<K, V> _kv;Colour _col;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;
};template<class K, class V>
struct 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* parent = nullptr;Node* cur = _root;while (cur)	//找插入位置{if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);//新节点的默认颜色是红色,因为如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整cur->_col = RED;if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfater = parent->_parent;assert(grandfater);assert(grandfater->_col == BLACK);// 关键看叔叔if (parent == grandfater->_left){Node* uncle = grandfater->_right;// 情况一 : uncle存在且为红,变色+继续往上处理if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfater->_col = RED;// 继续往上处理cur = grandfater;parent = cur->_parent;}// 情况二+三:uncle不存在 + 存在且为黑else{// 情况二:右单旋+变色//     g //   p   u// cif (cur == parent->_left){RotateR(grandfater);parent->_col = BLACK;grandfater->_col = RED;}else{// 情况三:左右单旋+变色//     g //   p   u//     cRotateL(parent);RotateR(grandfater);cur->_col = BLACK;grandfater->_col = RED;}break;}}else // (parent == grandfater->_right){Node* uncle = grandfater->_left;// 情况一if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfater->_col = RED;// 继续往上处理cur = grandfater;parent = cur->_parent;}else{// 情况二:左单旋+变色//     g //   u   p//         cif (cur == parent->_right){RotateL(grandfater);parent->_col = BLACK;grandfater->_col = RED;}else{// 情况三:右左单旋+变色//     g //   u   p//     cRotateR(parent);RotateL(grandfater);cur->_col = BLACK;grandfater->_col = RED;}break;}}}_root->_col = BLACK;return true;}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){if (_root == nullptr){return true;}if (_root->_col == RED){cout << "根节点不是黑色" << endl;return false;}// 黑色节点数量基准值int benchmark = 0;return PrevCheck(_root, 0, benchmark);}private:bool PrevCheck(Node* root, int blackNum, int& benchmark){if (root == nullptr){if (benchmark == 0){benchmark = blackNum;return true;}if (blackNum != benchmark){cout << "某条黑色节点的数量不相等" << endl;return false;}else{return true;}}if (root->_col == BLACK){++blackNum;}if (root->_col == RED && root->_parent->_col == RED){cout << "存在连续的红色节点" << endl;return false;}return PrevCheck(root->_left, blackNum, benchmark)&& PrevCheck(root->_right, blackNum, benchmark);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* ppNode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR){subLR->_parent = parent;}Node* ppNode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}
private:Node* _root = nullptr;
};

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

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

相关文章

Typecho COS插件实现网站静态资源存储到COS,降低本地存储负载

Typecho 简介Typecho 是一个简单、强大的轻量级开源博客平台&#xff0c;用于建立个人独立博客。它具有高效的性能&#xff0c;支持多种文件格式&#xff0c;并具有对设备的响应式适配功能。Typecho 相对于其他 CMS 还有一些特殊优势&#xff1a;包括可扩展性、不同数据库之间的…

(二十五)、实现评论功能(5)【uniapp+uinicloud多用户社区博客实战项目(完整开发文档-从零到完整项目)】

1&#xff0c;实现二级回复的入库操作 1.1 两个子组件&#xff08;comment-item和comment-frame&#xff09;与父组件reply之间的属性传值 comment-item&#xff1a; props: {item: {type: Object,default () {return {}}}},comment-frame&#xff1a; props: {commentObj: {…

儿童饰品发夹发卡出口美国办理什么认证?

亚马逊美国站上传新产品&#xff0c;很多时候都是需要类目审核的&#xff0c;后台给出要求提供认证&#xff0c;产品类目不同&#xff0c;所需要提供的认证证书是不一样&#xff0c;儿童产品需要提交的是CPC认证&#xff0c;玩具&#xff0c;母婴用品&#xff0c;儿童书包&…

PDF文件怎么转图片格式?转换有技巧

PDF文件有时为了更美观或者更直观的展现出效果&#xff0c;我们会把它转成图片格式&#xff0c;这样不论是归档总结还是存储起来都会更为高效。有没有合适的转换方法呢&#xff1f;这就来给你们罗列几种我个人用过体验还算不错的方式&#xff0c;大家可以拿来参考一下哈。1.用电…

Apifox = Postman + Swagger + Mock + JMeter

目录 可视化API设计 高效 & 零学习成本 可复用的“数据模型” 遵循 OpenAPI(Swagger) 规范 可导入 Swagger 等 20 数据格式 具体使用尝鲜 多项目管理 支持多环境切换 支持IDEA、浏览器、桌面应用 Idea插件 公共API hub库 如题&#xff1a;一款非常好用的API管理测…

Wi-Fi 7技术揭秘

引言 2022年4月7日&#xff0c;紫光股份旗下新华三集团全球首发企业级智原生Wi-Fi 7 AP新品 WA7638和WA7338。仅在同年的6月15日&#xff0c;在东京举行的第29届日本网络通信展览会&#xff08;Interop Tokyo 2022&#xff0c;简称Interop展&#xff09;中&#xff0c;WA7638就…

D1s RDC2022纪念版开发板开箱评测及点屏教程

作者new_bee 本文转自&#xff1a;https://bbs.aw-ol.com/topic/3005/ 目录 芯片介绍开发板介绍RT-Smart用户态系统编译使用感想引用 1. 芯片介绍 RISC-V架构由于其精简和开源的特性&#xff0c;得到业界的认可&#xff0c;近几年可谓相当热门。操作系统方面有RT-Thread&am…

Firebase常用功能和官方Demo简介

一、Firebase简介Firebase刚开始是一家实时后端数据库创业公司&#xff0c;它能帮助开发者很快的写出Web端和移动端的应用。自2014年10月Google收购Firebase以来&#xff0c;用户可以在更方便地使用Firebase的同时&#xff0c;结合Google的云服务。现在的Firebase算是谷歌旗下的…

模拟信号4-20mA /0-5V/0-75mV/0-100mV转RS-485/232,数据采集A/D转换模块 YL21

特点&#xff1a;● 模拟信号采集&#xff0c;隔离转换 RS-485/232输出● 采用12位AD转换器&#xff0c;测量精度优于0.1%● 通过RS-485/232接口可以程控校准模块精度● 信号输入 / 输出之间隔离耐压3000VDC ● 宽电源供电范围&#xff1a;8 ~ 32VDC● 可靠性高&#xff0c;编程…

界面控件DevExpress WinForm——轻松构建类Visual Studio UI(二)

DevExpress WinForm拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForm能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜任…

海思SD3403/SS928V100开发(5)MIPI_YUV相机vio sample开发----修改思路

1. 前言 sensor输出格式: YUV422 8bit 硬件连接: MIPI_YUV相机(4lane MIPI) -> SS928V100 MIPI0(4lane) 框图: 2. 几个问题 基于SS928 SDK中的 vio sample修改; 但是sample里面都是基于RAW RGB sensor开发的sample, 没有现成的MIPI_YUV sensor的参考,需要自己…

CentOS 8利用Apache安装部署下载服务器

1&#xff1a;部署的目的是做一个类似下面开源镜像网站&#xff0c;把一些软件或者资料上传到服务器上面&#xff0c;减少用户在互联网上下载资料&#xff0c;提高效率&#xff0c;减少病毒。 2&#xff1a;使用下面的命令配置本机的IP地址主机名等信息。后期使用IP地址进行访问…

11技术太卷我学APEX-数据加载

11技术太卷我学APEX-数据加载 0 所谓的数据加载 就是导入数据到数据库表中&#xff0c;本示例就采用Excel导入数据到《技术太卷我学APEX》的apex_learn表。表结构大概是这样的 CREATE TABLE "APEX_LEARN" ( "P_ID" NUMBER(17,0) NOT NULL ENABLE, &quo…

【Elasticsearch】安装配置与使用

一、下载与安装Elasticsearch下载地址&#xff1a;https://www.elastic.co/cn/downloads/elasticsearch#ga-release前端管理界面&#xff1a;https://github.com/mobz/elasticsearch-head这两个文件都是解压即可。二、配置与启动1.elasticsearch6以上的版本已经内置jdk&#xf…

2023年Java进阶架构师的必备思维导图,让你少走弯路!

架构师是什么&#xff1f;要做什么&#xff1f; 架构师 &#xff1a;是一个既需要掌控整体又需要洞悉局部瓶颈并依据具体的业务场景给出解决方案的团队领导型人物。架构师不是一个人&#xff0c;他需要建立高效的体系&#xff0c;带领团队去攻城略地&#xff0c;在规定的时间内…

一文速学-Adaboost模型算法原理以及实现+Python项目实战

目录 前言 一、Adaboost算法概述 二、Adaboost模型原理 类推 计算原理 特点 适应较小量数据集&#xff0c;训练时间长 三、Python实例运用 AdaBoostClassifier分类 参数 实例实现分类 导入数据集 划分数据集 训练模型 评估算法 模型效果 前言 集成学习的方法在全…

[NOIP2002 普及组] 过河卒

题目描述&#xff1a; 棋盘上 A 点有一个过河卒&#xff0c;需要走到目标 B 点。卒行走的规则&#xff1a;可以向下、或者向右。同时在棋盘上 C 点有一个对方的马&#xff0c;该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。 棋盘用坐标表…

干测试5年,经常被开发看不起,现在总算证明了自己····

测试不止是点点点 我感觉我是一个比较有发言权的人吧&#xff0c;我在测试行业摸爬滚打5年&#xff0c;以前经常听到开发对我说&#xff0c;天天的点点点有意思没&#xff1f; 和IT圈外的同学、朋友聊起自己的工作&#xff0c;往往一说自己是测试&#xff0c;无形中也会被大家…

利用global mapper导出等高线 在sketch up制作三维模型

为了做一个地形模型&#xff0c;绞尽脑汁实验了所有能查到的教程&#xff0c;在免费的基础上总体尝试失败&#xff0c;一是需要花钱的插件例如bitmap to mesh&#xff0c;即便能下载到&#xff0c;也是无法安装使用。如果你能下到且安装上&#xff0c;别忘了分享给我。 二是有的…

什么是品牌营销?学会正确推广您的业务

什么是品牌营销&#xff1f; 品牌营销涉及长期战略规划&#xff0c;以推广整个品牌&#xff0c;而不是营销单个产品或服务。它分享了一个引人入胜的故事&#xff0c;以在潜在客户中产生品牌知名度并建立声誉。 面向消费者的品牌使用品牌智能软件来了解人们对其品牌的看法&#…