C++进阶 | [4.3] 红黑树

摘要:什么是红黑树,模拟实现红黑树

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

1. 红黑树的性质

注意:红黑树是一种搜索二叉树。性质如下:

① 红黑树的根节点为 Black ;

Red 节点不连续,即 Red 节点一定有 Black 的孩子节点;

③ 每条路径含有相同数量的 Black 节点;(关于这点下文会详细解释)

④ 每个节点要么是 Black 的要么是 Red 的;

nullptr 节点为 Black(在红黑树中这样的节点被称为NIL节点,这样是为了方便找准路径,非硬性要求)

  • 路径 - path从根节点到空,称为一条路径。
    最短路径:如果一条路径的所有节点均为 Black ,则该路径为最短路径;
    最长路径: Black Red Black Red Black Red Black ……Red NIL 这样总是黑红节点相间的路径为最长路径。(注意:路径的结尾为空节点,而空节点一定是 Black 的,又因为红黑树的每条路径含有数量相同的 Black 节点,因此可得最长路径=最短路径×2-1

如图所示,path1和path2为该红黑树的最短路径;path8为该红黑树的最长路径。红黑树中的叶节点为特殊的叶节点,即 nullptr 节点,在此图中写作“NIL”。
与AVL树比较,AVL树是更接近满二叉树的严格平衡,而红黑树是近似平衡。


2. 模拟实现红黑树

1)框架

同样的,先定义红黑树的节点。根据红黑树对节点的“颜色”要求,这里采用枚举(enum)类型来表达节点的“颜色”。示例代码如下。

ps.默认新创建的节点为红色(下文会解释这样做的原因)。

enum Colour
{RED, BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode(T data = T()):_data(data), _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _col(RED)//默认新创建的节点都是红色的{}T _data;RBTreeNode<T>* _pLeft;RBTreeNode<T>* _pRight;RBTreeNode<T>* _pParent;Colour _col;
};// 模拟实现红黑树的插入--注意:为了后序封装map和set,在实现时给红黑树多增加了一个头结点
template<class T>
class RBTree
{typedef RBTreeNode<T> Node;
public:RBTree(){_pHead = new Node;_pHead->_pLeft = _pHead;_pHead->_pRight = _pHead;}private:Node* _pHead;
};

2)insert

红黑树的插入函数实现的思路同AVL树是类似的。

-🟡Part1.插入新节点-   红黑树首先是一个BST,先按BST的规则插入新节点。代码如下。注意:当我们插入新的节点时,总是默认新插入的节点为红色。理由是:如果在红黑树中插入黑色节点将对整棵红黑树造成影响,因为红黑树要求每条路径上含有数量相同的黑色节点,一旦插入黑色节点必然要应对红黑树的调整,而插入红色节点,若该新节点的parent节点不为红色节点则不用调整这棵红黑树。

// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false.注意:为了简单起见,本次实现红黑树不存储重复性元素
bool Insert(const T& data)
{if (_pHead->_pLeft == _pHead && _pHead->_pRight == _pHead){Node* _pNewNode = new Node(data);_pHead->_pLeft = _pHead->_pRight = _pNewNode;_pNewNode->_pParent = _pHead;_pNewNode->_col = BLACK;//跟节点为黑色return true;}Node* pCur = _pHead->_pLeft;Node* pParent = pCur->_pParent;while (pCur){pParent = pCur;if (pCur->_data > data){pCur = pCur->_pLeft;}else if (pCur->_data < data){pCur = pCur->_pRight;}else{return false;}}Node* _pNewNode = new Node(data);if (pParent->_data > data)pParent->_pLeft = _pNewNode;elsepParent->_pRight = _pNewNode;_pNewNode->_pParent = pParent;//……return true;
}

-🟡Part2.调整红黑树-  由于插入的新节点默认为红色,因此当插入导致出现连续的红色节点时,需要调整红黑树。 

如上图。下一步,继续对圈出的subtree进行展开:👇

  • 情况一:当圈出的subtree的根节点 psibling 为红色,这种情况不需要旋转,直接调整节点颜色即可。从下图的换色中不难看出,这样换色之后,每个路径的黑色节点数量不受影响。注意:当 pGrandparent 变为红色后,可能会与 其parent 形成连续的红色节点,因此需要继续向上操作,即令 pCur = pGrandparent(赋值操作),继续判断,并在必要的情况下调整。(这个“向上的”思路和AVL树的调整是类似的)
    ps.pParent 不一定是 pGrandparent 的节点,pCur 不一定就是 pParent 的节点。这里之所以分析下图中的这一种情况是因为,这类情况的调整不需要旋转,即直接调整节点颜色——pParent和psibling都变红,因此这两个节点相对于pGrandparent的左右位置不重要,pCur不做改变,因此pCur相对于pParent的左右位置也不重要。但需要旋转调整的情况就不一样了,pParent 、pGrandparent 、pCur 这三个节点之间的关系决定了旋转的方向,下面“情况二”会详细讲解。

  • 情况二:当圈出的subtree根节点为黑色,这种情况需要旋转+调色,下面对这种情况继续细分。👇
    ps.这棵被圈出的 subtree 黑色节点数目为n,在情况二这种情况下,psibing已经是一个黑色节点了,因此其子树黑色节点为n-1,如图中所写。(n≥1) 另外,注意 psibing 可能为NIL,即黑色的空节点。

    如下图,第三列表示调色后的结果,调色思路可以归纳为——旋转后的树的新根变为黑色,旋转前的树的旧根变为红色。

示例代码:

{//RBTree// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false.注意:为了简单起见,本次实现红黑树不存储重复性元素bool Insert(const T& data){//前面插入新节点的部分省略///新插入的节点默认是红色的,因此若在这条含有新插入节点的路径中出现·连·续·红·色·节点则需要调整///pCur = _pNewNode;//以新插入的结点为起点向上调整while (pCur->_col == RED && pParent->_col == RED){Node* pGrandparent = pParent->_pParent;Node* pP_sibling = pGrandparent->_pLeft == pParent ? pGrandparent->_pRight : pGrandparent->_pLeft;if (pP_sibling && pP_sibling->_col == RED)//调色{pParent->_col = pP_sibling->_col = BLACK;if (pGrandparent != _pHead->_pLeft){pGrandparent->_col = RED;pCur = pGrandparent;pParent = pCur->_pParent;}}else//旋转+调色{if (pCur == pParent->_pLeft){if (pParent == pGrandparent->_pLeft)//右旋{RotateR(pGrandparent);pParent->_col = BLACK;}else//右左双旋{RotateR(pParent);RotateL(pGrandparent);//双旋后pCur成为新的subrootpCur->_col = BLACK;}pGrandparent->_col = RED;}else if (pCur == pParent->_pRight){if (pParent == pGrandparent->_pRight)//左旋{RotateL(pGrandparent);//左旋后pParent成为新的subrootpParent->_col = BLACK;}else//左右双旋{RotateL(pParent);RotateR(pGrandparent);pCur->_col = BLACK;}pGrandparent->_col = RED;}}}return true;}private:// 左单旋void RotateL(Node* subroot){Node* pGrandparent = subroot->_pParent;Node* subR = subroot->_pRight;Node* subR_L = subR->_pLeft;if (pGrandparent == _pHead){pGrandparent->_pLeft = pGrandparent->_pRight = subR;}else{if (subroot == pGrandparent->_pLeft){pGrandparent->_pLeft = subR;}elsepGrandparent->_pRight = subR;}subR->_pParent = pGrandparent;subR->_pLeft = subroot;subroot->_pParent = subR;subroot->_pRight = subR_L;if (subR_L)subR_L->_pParent = subroot;}// 右单旋void RotateR(Node* subroot){Node* pGrandparent = subroot->_pParent;Node* subL = subroot->_pLeft;Node* subL_R = subL->_pRight;if (pGrandparent == _pHead){pGrandparent->_pLeft = pGrandparent->_pRight = subL;}else{if (subroot == pGrandparent->_pLeft){pGrandparent->_pLeft = subL;}elsepGrandparent->_pRight = subL;}subL->_pParent = pGrandparent;subroot->_pLeft = subL_R;if (subL_R)subL_R->_pParent = subroot;subL->_pRight = subroot;subroot->_pParent = subL;}
};

ps.psibling 这个节点是有可能为空结点的,写旋转函数的时候要注意!

3)判断红黑树的合法性

要判断红黑树是否合法就需要比对着红黑树的性质一条一条来确认:

① 红黑树的根节点为 Black ;(⭕需要去判断树的根节点的颜色

Red 节点不连续,即 Red 节点一定有 Black 的孩子节点;(⭕需要验证

③ 每条路径含有相同数量的 Black 节点;(⭕需要验证

④ 每个节点要么是 Black 的要么是 Red 的;(✅通过enum类型来确保节点黑或红且无其他颜色可能

nullptr 节点为 Black。(✅这条显然不必验证

思路:选择一条路径并的到这条路径的黑色节点数量,并这条路径的黑色节点数量为标准,判断其他任以路径的黑色节点数量是否与其相等。简单起见,选择最左路径的黑色节点数量为标准数量去比对别的路径的黑色节点数量。

由上,关键问题在于如何计算出所有路径(除选择的标准路径)的外的黑色节点。以下图为例进行分析。

代码示例: 

// 检测红黑树是否为有效的红黑树
bool IsValidRBTRee()
{Node* pCur = _pHead->_pLeft;if (pCur == nullptr)return true;if (pCur->_col == RED)return false;size_t blackNUM = 0;while (pCur){if (pCur->_col == BLACK)++blackNUM;pCur = pCur->_pLeft;}++blackNUM;//算上最后的空节点size_t pathblack = 0;return _IsValidRBTRee(_pHead->_pLeft, blackNUM, pathblack);
}bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack)
{if (pRoot == nullptr){++pathBlack;//算上最后的空节点if (blackCount == pathBlack)return true;elsereturn false;}if (pRoot->_col == BLACK){++pathBlack;return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack)&& _IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);}else{if ((pRoot->_pLeft && pRoot->_pLeft->_col == RED)|| (pRoot->_pRight && pRoot->_pRight->_col == RED)){cout << "错误:存在连续的红色节点" << "->" << pRoot->_data << "->该节点附近存在连续红色节点" << endl;return false;}else{return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack)&& _IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);}}
}

END

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

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

相关文章

【RT摩拳擦掌】基于RT106L/S语音识别的百度云控制系统

【RT摩拳擦掌】基于RT106L/S语音识别的百度云控制系统 一 文档简介二 平台构建2.1 使用平台2.2 百度智能云2.2.1 物联网核心套件2.2.2 在线语音合成 2.3 playback语音数据准备与烧录2.4 开机语音准备与添加2.5 唤醒词识别词命令准备与添加 三 代码准备3.1 sln-local/2-iot 代码…

cube-studio开源一站式机器学习平台,在线ide,jupyter,vscode,matlab,rstudio,ssh远程连接,tensorboard

全栈工程师开发手册 &#xff08;作者&#xff1a;栾鹏&#xff09; 一站式云原生机器学习平台 前言 开源地址&#xff1a;https://github.com/tencentmusic/cube-studio cube studio 腾讯开源的国内最热门的一站式机器学习mlops/大模型训练平台&#xff0c;支持多租户&…

什么是原始权益人?

摘要&#xff1a;每天学习一点金融小知识 原始权益人&#xff0c;在资产证券化&#xff08;ABS&#xff09;和公募REITs等金融产品中&#xff0c;指的是证券化基础资产的原始所有者&#xff0c;即金融产品的真正融资方。他们是按照相关规定及约定向资产支持专项计划转移其合法拥…

Mysql面试合集

概念 是一个开源的关系型数据库。 数据库事务及其特性 事务&#xff1a;是一系列的数据库操作&#xff0c;是数据库应用的基本逻辑单位。 事务特性&#xff1a; &#xff08;1&#xff09;原子性&#xff1a;即不可分割性&#xff0c;事务要么全部被执行&#xff0c;要么就…

基于决策树的旋转机械故障诊断(Python)

前置文章&#xff1a; 将一维机械振动信号构造为训练集和测试集&#xff08;Python&#xff09; https://mp.weixin.qq.com/s/DTKjBo6_WAQ7bUPZEdB1TA 旋转机械振动信号特征提取&#xff08;Python&#xff09; https://mp.weixin.qq.com/s/VwvzTzE-pacxqb9rs8hEVw import…

数据库定义语言(DDL)

数据库定义语言&#xff08;DDL&#xff09; 一、数据库操作 1、 查询所有的数据库 SHOW DATABASES;效果截图&#xff1a; 2、使用指定的数据库 use 2403 2403javaee;效果截图&#xff1a; 3、创建数据库 CREATE DATABASE 2404javaee;效果截图&#xff1a; 4、删除数据…

Web端登录页和注册页源码

前言&#xff1a;登录页面是前端开发中最常见的页面&#xff0c;下面是登录页面效果图和源代码&#xff0c;CV大法直接拿走。 1、登录页面 源代码&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title>登录</ti…

springboot汽车租赁管理系统08754

目 录 摘 要 第 1 章 引 言 1.1 选题背景和意义 1.2 国内外研究现状 1.3 论文结构安排 第 2 章 系统的需求分析 2.1 系统可行性分析 2.1.1 技术方面可行性分析 2.1.2 经济方面可行性分析 2.1.3 法律方面可行性分析 2.1.4 操作方面可行性分析 2.2 系统功能需求分析…

视频监控EasyCVR视频汇聚/智能边缘网关:EasySearch无法探测到服务器如何处理?

安防监控EasyCVR智能边缘网关/视频汇聚网关/视频网关属于软硬一体的边缘计算硬件&#xff0c;可提供多协议&#xff08;RTSP/RTMP/国标GB28181/GAT1400/海康Ehome/大华/海康/宇视等SDK&#xff09;的设备接入、音视频采集、视频转码、处理、分发等服务&#xff0c;系统具备实时…

华为防火墙在广电出口安全方案中的应用(方案设计、配置、总结)

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 你们好&#xff0c;我的网工朋友。 不知道你有没有想过&#xff0c;我们每天看电视、上网追剧的广电网络&#xff0c;它的背后是如何确保安全稳定…

RANSAC空间圆拟合实现

由初中的几何知识我们可以知道&#xff0c;确定一个三角形至少需要三个不共线的点&#xff0c;因此确定一个三角形的外接圆至少可用三个点。我们不妨假设三个点坐标为P1(x1,y1,z1),P2(x2,y2,z2),P3(x3,y3,z3)。 圆方程的标准形式为&#xff1a; (xi-x)2(yi-y)2R2 &#xff08;1…

黑马点评下订单-小程序下单没问题但是Postman发送请求失败了,返回401

经过多方探索&#xff0c;这个✓8错误就是由于黑马点评使用了拦截器&#xff0c;我们直接发送请求是会被拦截器拦截下来的&#xff0c;我给出的解决方案是通过配置Postman解决&#xff0c;方法很简单&#xff01; 解决方案 右边的value写上Redis里面登录所用token值就可以了…

MSPG3507——蓝牙接收数据显示在OLED,滴答定时器延时500MS

#include "ti_msp_dl_config.h" #include "OLED.h" #include "stdio.h"volatile unsigned int delay_times 0;//搭配滴答定时器实现的精确ms延时 void delay_ms(unsigned int ms) {delay_times ms;while( delay_times ! 0 ); } int a0; …

昇思25天学习打卡营第10天|FCN图像语义分割

一、简介&#xff1a; 本篇博客是昇思大模型打卡营应用实践部分的第一次分享&#xff0c;主题是计算机视觉&#xff08;CV&#xff09;领域的FCN图像语义分割&#xff0c;接下来几天还会陆续分享其他CV领域的知识&#xff08;doge&#xff09;。 全卷积网络&#xff08;Fully…

微信小程序-插槽slot

一.插槽slot 在页面使用自定义组件的时候&#xff0c;如果在自定义组件里面写子组件&#xff0c;子组件的内容无法显示。 <custom01> <text slotslot-top>你好&#xff0c;上方组件</text> 你好&#xff0c;组件 <text slotslot-bottom>你好&#xf…

【从0实现React18】 (五) 初探react mount流程 完成核心递归流程

更新流程的目的&#xff1a; 生成wip fiberNode树标记副作用flags 更新流程的步骤&#xff1a; 递&#xff1a;beginWork归&#xff1a;completeWork 在 上一节 &#xff0c;我们探讨了 React 应用在首次渲染或后续更新时的整体更新流程。在 Reconciler 工作流程中&#xff…

Nginx 配置文件

Nginx的配置文件的组成部分&#xff1a; 主配置文件&#xff1a;nginx.conf子配置文件&#xff1a;include conf.d/*.conf 全局配置 nginx 有多种模块 核心模块&#xff1a;是 Nginx 服务器正常运行必不可少的模块&#xff0c;提供错误日志记录 、配置文件解析 、事件驱动机…

32.哀家要长脑子了!

1.299. 猜数字游戏 - 力扣&#xff08;LeetCode&#xff09; 公牛还是挺好数的&#xff0c;奶牛。。。妈呀&#xff0c;一朝打回解放前 抓本质抓本质&#xff0c;有多少位非公牛数可以通过重新排列转换公牛数字&#xff0c;意思就是&#xff0c;当这个数不是公牛数字时&#x…

怎样打造交互式3D数据可视化?

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 基于Plotly.js的交互式散点图和直方图联动 应用场景介绍 本代码演示了如何使用Plotly.js库创建交互式散点图和直方图联动&#xff0c;允许用户通过套索选择散点图中的数据点&#xff0c;并实时更新直方图以显…

大促前夕即高点,综合电商平台的“稀缺”魔法正在消失?

新一期618大促早已结束良久了&#xff0c;但似乎其产生的余韵却仍旧未消散。 从最直观的资本市场走势来看&#xff0c;自这一波618大促陆续开展之后&#xff0c;包括京东、阿里巴巴、拼多多等港美股股价就一改此前的上行态势&#xff0c;持续下滑至今。 事实上&#xff0c;早…