c++ 红黑树(带头结点)

想必在看到这篇文章的时候,你一定是带着问题去搜索的,一定是对红黑树已经有了初步大致的认识,已经知道了红黑树的性质与普通红黑树的功能与如何代码实现,但是莫一天突然看到了带头结点的红黑树,肯定是对此有一些疑惑的,或者来说在代码的实现上自己存在着某些疑惑。那么话不多说,就先给出红黑树(带头结点)的完整实现代码。然后再给出相应的详细解释。

代码的实现

#include<iostream>
#include<vector>
using namespace std;
enum Colour
{RED,BLACK,
};template<class T>
struct RBTreeNode
{RBTreeNode<T>* _pLeft;RBTreeNode<T>* _pRight;RBTreeNode<T>* _pParent;T _data;Colour _col;RBTreeNode(const T& data = T(), Colour col = RED): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _col(col){}
};// 请模拟实现红黑树的插入--注意:为了后序封装map和set,本文在实现时给红黑树多增加了一个头结点
template<class T>
class RBTree
{typedef RBTreeNode<T> Node;
public:RBTree(){_pHead = new Node;_pHead->_pLeft = _pHead;_pHead->_pRight = _pHead;}// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false// 注意:为了简单起见,本次实现红黑树不存储重复性元素bool Insert(const T& data){Node*& pRoot = GetRoot();// 第一次插入结点if (pRoot == nullptr){pRoot = new Node(data, BLACK);//pRoot->_pParent = _pHead;return true;}// 找待插入节点在二叉搜索树中的位置// 并保存其双亲节点else{Node* pCur = pRoot;Node* pParent = nullptr;while (pCur){pParent = pCur;if (data < pCur->_data)pCur = pCur->_pLeft;else if (data > pCur->_data)pCur = pCur->_pRight;elsereturn false;}// 插入新节点pCur = new Node(data);if (data < pParent->_data)pParent->_pLeft = pCur;elsepParent->_pRight = pCur;pCur->_pParent = pParent;//调整// l pCur新节点默认颜色 : 红色// 如果pParent的颜色是黑色的,满足红黑树性质// 如果pParent的颜色是红色的,违反了性质三--不能有连在一起的// ...while (pParent != _pHead && pParent->_col == RED)//大前提{Node* grandfather = pParent->_pParent;//parent在左if (pParent == grandfather->_pLeft){Node* uncle = grandfather->_pRight;//Node* uncle = pParent->_pRight;//错误二if (uncle && uncle->_col == RED){//情景一:pCur->红,pParent->红,grandfather->黑,uncle存在且为红//     g//   p   u// c// //解决:p,u改为黑,g改为红,最后g为红所以,要继续向上调整pParent->_col = uncle->_col = BLACK;grandfather->_col = RED;//pCur = grandfather;if (pCur == pRoot){pCur->_pParent = _pHead;}pParent = pCur->_pParent;}else{if (pCur == pParent->_pLeft){//情景二:pCur->红,pParent->红,grandfather->黑,uncle不存在/为黑//     g//   p   u// c// // 解决:对g右单旋,p改为黑,g改为红,最后g为黑所以,直接breakRotateR(grandfather);pParent->_col = BLACK;grandfather->_col = RED;}else{//情景三:pCur->红,pParent->红,grandfather->黑,uncle不存在/为黑//       g//   p      u//     c// 解决:对p左单旋,后变为情景二。RotateL(pParent);RotateR(grandfather);pCur->_col = BLACK;grandfather->_col = RED;}break;}}else//情景大概反着来{//1  uncleNode* uncle = grandfather->_pLeft;//错误一//Node* uncle = pParent->_pRight;//错误一if (uncle && uncle->_col == RED){pParent->_col = uncle->_col = BLACK;grandfather->_col = RED;pCur = grandfather;/if (pCur == pRoot){pCur->_pParent = _pHead;}pParent = pCur->_pParent;}else{if (pCur == pParent->_pRight)//2{RotateL(grandfather);pParent->_col = BLACK;grandfather->_col = RED;}else//3{RotateR(pParent);RotateL(grandfather);pCur->_col = BLACK;grandfather->_col = RED;}break;}}}Node*& _root = GetRoot();//最后_root->_col = BLACK;return true;}}// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptrNode* Find(const T& data){Node* pCur = GetRoot();while (pCur){if (pCur->_data == data)break;else if (pCur->_data > data)pCur = pCur->_pLeft;elsepCur = pCur->_pRight;}return pCur;}// 获取红黑树最左侧节点Node* LeftMost(){Node* pCur = GetRoot();if (nullptr == pCur)return _pHead;while (pCur->_pLeft){pCur = pCur->_pLeft;}return pCur;}// 获取红黑树最右侧节点Node* RightMost(){Node* pCur = GetRoot();if (nullptr == pCur)return _pHead;while (pCur->_pRight){pCur = pCur->_pRight;}return pCur;}// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测bool IsValidRBTRee(){//1:是否存在 红-红 //每条路径黑色结点是否相同个数//最长的不超过最短的二倍//根,叶子为黑Node* _root = GetRoot();if (nullptr == _root)return true;if (_root->_col == RED)return false;Node* pCur = _root;size_t refVal = 0;while (pCur){if (pCur->_col == BLACK)refVal++;pCur = pCur->_pLeft;}size_t blacknum = 0;return _IsValidRBTRee(_root, blacknum, refVal);}
private:bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack){if (pRoot == nullptr){if (pathBlack != blackCount){cout << "存在黑色节点数量不相等的路径" << endl;return false;}return true;}Node* pParent = pRoot->_pParent;if (pRoot->_col == RED){if (pParent != _pHead && pRoot->_pParent->_col == RED){cout << "有连续的红色节点" << endl;return false;}}if (pRoot->_col == BLACK){++blackCount;}return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack)&& _IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);}// 左单旋void RotateL(Node* pParent){Node*& _root = GetRoot();Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;pParent->_pRight = subRL;subR->_pLeft = pParent;Node* parentParent = pParent->_pParent;pParent->_pParent = subR;if (subRL)subRL->_pParent = pParent;if (_root == pParent){_root = subR;subR->_pParent = nullptr;}else{if (parentParent->_pLeft == pParent){parentParent->_pLeft = subR;}else{parentParent->_pRight = subR;}subR->_pParent = parentParent;}}// 右单旋void RotateR(Node* pParent){Node*& _root = GetRoot();Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;pParent->_pLeft = subLR;if (subLR)subLR->_pParent = pParent;Node* parentParent = pParent->_pParent;subL->_pRight = pParent;pParent->_pParent = subL;if (_root == pParent){_root = subL;subL->_pParent = nullptr;}else{if (parentParent->_pLeft == pParent){parentParent->_pLeft = subL;}else{parentParent->_pRight = subL;}subL->_pParent = parentParent;}}// 为了操作树简单起见:获取根节点Node*& GetRoot(){return _pHead->_pParent;}
private:Node* _pHead;
};

对应的代码解释与红黑树(带头结点)的讲解

带头结点红黑树中头节点设计的解释

首先需要解释一点:本红黑树类内部只有一个私有成员:头节点。

我们知道set与map的底层就为红黑树,其就是由红黑树经过封装后得到的,那么封装这个过程其实存在着很多的细节,这些细节既复杂又多,这就使得如果直接进行封装步骤就很麻烦,那么对于此就出现了为了后序封装set与map,就出现了带头结点的红黑树。

对于带头结点的红黑树,如果对于此一点也不了解,那么其实对于一个刚学完红黑树的会存在很多的疑惑,就比如说带哨兵位的链表中,头节点是一个真是存在的一个结点,其与正常结点的区别就是其里面存储的信息是0或者空,其指针的设计与普通结点无异。如果还按照这样设计,那么红黑树的头节点也应该如此,那么这就会引出疑问

  1. 头节点的颜色如何设计?
  2. 头节点的三叉链如何设计?
  3. 头节点与根结点的具体关系如何设计?

问题1

第一个问题的答案无非是红或者黑,如果我们设计为红色,那么他要符合其中一个性质:其两个孩子全为黑色,这又怎么设计呢?因为头节点他仅有一个孩子,根节点。那么对此也只有可能设计为黑色。

问题2

对于头结点的三叉链怎么设计,我们要保证其三个指针全都设计合理,那么对此搜集了一些材料,得知其左指针与右指针全都指向自己。

其此设计的原因如下:

  1. 简化边界条件:在进行插入、删除等操作时,通常需要处理树的边界情况。通过让头节点的左右指针指向自己,可以避免处理空树的特殊情况,使得算法更加统一和简洁。

  2. 统一处理:在遍历树或查找节点时,头节点作为一个特殊的节点,可以统一处理所有节点的情况。无论是从头节点开始还是从其他节点开始,操作逻辑都可以保持一致。

  3. 避免空指针:如果头节点的左右指针指向空(NULL),在某些操作中可能会导致空指针异常。通过指向自己,可以确保在任何情况下都有一个有效的指针,减少了出错的可能性。

  4. 便于实现:在实现红黑树的各种操作时,特别是旋转和调整颜色等操作,使用头节点指向自己可以减少代码中的条件判断,使得实现更加简洁。

总之,这种设计使得红黑树的实现更加高效和易于维护。

那么对应的父亲结点也只能指向其根结点了。

代码实现的部分解释

其本代码实现的思路完全与不带头结点的红黑树的整体思路是完全一致的,跟上篇文章一样,插入总体分为两大类,第二类又分为四小情况。

针对于第一次插入,我们再进行第一次插入时,此时为特殊情况,我们要获取头节点的父亲指针指向的空间是否为空为标准进行判断是否为第一次插入,那么对于此,就要特殊设计一个函数GetRoot();

	Node*& GetRoot(){return _pHead->_pParent;}

其余代码实现起来没什么难度,这里就不多说了,有疑惑的看上面的代码。

其余插入代码跟红黑树(普通)插入无疑,思路理清楚,整体实现起来还是不难的,我自己在写本代码的时候,就是先粘贴以前的注释,然后再一点一点写,不会的再去翻一翻以前的代码实现起来用了一个多小时。

代码注意事项

在写完代码的时候,在第一次测试的时候就遇到了指向空的报错,然后经过代码长达半个小时的调试与各处调整小细节,才调好了第一次解决了指向空的报错。

问题如下:

问题一

在此我们要写pParent != _pHead;

 而不是写pParent这是因为根节点的pParent是空,如果还这样代码走下去,那么导致后面

这是因为我们没有把头节点当作头节点,而是把他当作根结点一样处理了,这肯定会导致错误。

问题二 

这个问题结果过后,在代码看起来运行起来没有问题,但是对红黑树进行检测,还是存在着问题,他会报错

这个问题如果经过详细的一步一步进行代码调试也是可以发现的,就是没有添加如下代码部分,

这里的pRoot为:

 

其实就是如果跟换根节点要同时更换头节点的_pParent指针的指向。使其指向新的根节点。 

我写这部分代码的时候遇见的就是这两个大问题,一些小问题就是一些代码拼写错误,还有一些书写问题,与旋转过后颜色调整错误,这些都是些小问题,经过简单调试就可以完成不用调试那么仔细。

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

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

相关文章

匹配行最大值替换为最小值公式

好的!我们一步一步详细讲解这个公式的作用和如何实现你想要的功能。 ### 数据结构假设: - 你的数据在 A、B、C 列中,每一行都有值。 - 需要在 A 列和 B 列相同的行中,找到 C 列中的最大值,将其替换为最小值,其他值保持不变。 ### 公式: ```excel =IF(C2=MAX(IF(($A$2:$…

Clickhouse使用笔记

clickhouse官方文档&#xff1a;https://clickhouse.com/docs/zh/sql-reference/data-types/decimal 一&#xff0c;建表 create table acitivity_user_record ( id String DEFAULT generateUUIDv4(), -- 主键自增 activityId String, userId String, userName Nullable(Strin…

Git之误执行git rm -r解决方案(六十七)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

9月16日星期一今日早报简报微语报早读

9月16日星期一&#xff0c;农历八月十四&#xff0c;早报微语早读。 1、猫眼专业版数据&#xff1a;2024年中秋档票房破亿&#xff1b; 2、WTT澳门冠军赛&#xff1a;孙颖莎夺得女单冠军&#xff1b; 3、乐山夹江县&#xff1a;支持农村居民进城购房&#xff0c;每户补贴5万…

天融信把桌面explorer.exe删了,导致开机之后无windows桌面,只能看到鼠标解决方法

win10开机进入桌面&#xff0c;发现桌面无了&#xff0c;但是可以ctrlaltdelete调出任务管理器 用管理员权限打开cmd&#xff0c;输入&#xff1a; sfc /scanfilec:\windowslexplorer.exe 在运行C:\windows\Explorer.exe&#xff1b;可以进入桌面&#xff0c;但是隔离几秒钟…

Java多线程1

目录 1.简述进程与线程之间的主要差异。 2.描述进程间通信的常用方式。 3.详细说明线程间如何进行通信。 4.什么是原子性&#xff1f;请举例说明。 5.i 和 i--操作是否具有原子性&#xff1f;为什么&#xff1f; 1.简述进程与线程之间的主要差异。 进程和线程是计算机系统…

MYSQL基础-多表操作-事务-索引

1. 多表设计 概述 项目开发中&#xff0c;在进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;由于业务之间相互关联&#xff0c;所以各个表结构之间也存在着各种联系&#xff0c;基本上分为三种&#xff1a; …

Python3时间模块使用

文章目录 python安装时间处理模块概述time 模块常用方法 datetime 模块常用方法 时间戳与 datetime 的相互转换时区处理使用 pytz 设置时区 实际应用场景日志时间处理时间差计算不同时区的时间转换 结论 在 Python 编程中&#xff0c;时间处理和时间格式转换是非常常见的需求&a…

美团图床设置教程

大厂图床&#xff0c;CDN加速 项目地址&#xff1a;https://github.com/woniu336/mt-img 使用方法 在mt.php填上你的token即可&#xff0c;然后打开index.html上传图片 获取token方法 注册https://czz.meituan.com/发布视频&#xff0c;上传封面&#xff0c;注意在上传封面后…

【退役之再次线上部署】Spring Boot + VUE + Nginx + MySQL

这篇博客写在凌晨 4 点 20 分&#xff0c;这个时候我刚线上部署完成 web 项目&#xff0c;自己写的全栈项目 这个点儿&#xff0c;也睡不着了&#xff0c;索性就写篇博客记录一下 一、踩坑实录 这个是 最重要的&#xff0c;所以写在前面 Nginx 配置文件 location location /a…

【更新】上市公司-供应链金融水平数据(2000-2023年)

上市公司供应链金融是指上市公司利用其产业链核心地位&#xff0c;通过整合金融资源&#xff0c;为供应链上下游企业提供包括融资、结算、风险管理等在内的综合金融服务。为了衡量上市公司的供应链金融水平&#xff0c;参考周兰等&#xff08;2022&#xff09;的研究方法&#…

基于Spring Boot的心理健康服务系统的设计与实现(毕业论文)

目    录 1 前言 1 1.1 研究目的与意义 1 1.2 国内外研究现状 1 1.3 论文结构 3 2 可行性分析 3 3 系统需求分析 4 3.1 用户需求分析 4 3.2 心理咨询师需求分析 5 3.3 管理员需求分析 6 3.4 业务流程分析 7 4 概要设计 9 4.1 系统结构设计 10 4.2 功能模块设计 10 4.2.1 管…

20Kg载重30分钟续航多旋翼无人机技术详解

一、机架与结构设计 1. 材料选择&#xff1a;为了确保无人机能够承载20Kg的负载&#xff0c;同时实现30分钟的续航&#xff0c;其机架材料需选用轻质高强度的材料&#xff0c;如碳纤维或铝合金。这些材料不仅具有良好的承重能力&#xff0c;还能有效减轻无人机的整体重量&…

计算机毕业设计Python深度学习垃圾邮件分类检测系统 朴素贝叶斯算法 机器学习 人工智能 数据可视化 大数据毕业设计 Python爬虫 知识图谱 文本分类

基于朴素贝叶斯的邮件分类系统设计 摘要&#xff1a;为了解决垃圾邮件导致邮件通信质量被污染、占用邮箱存储空间、伪装正常邮件进行钓鱼或诈骗以及邮件分类问题。应用Python、Sklearn、Echarts技术和Flask、Lay-UI框架&#xff0c;使用MySQL作为系统数据库&#xff0c;设计并实…

Gitlab 中几种不同的认证机制(Access Tokens,SSH Keys,Deploy Tokens,Deploy Keys)

前言 公司主要使用 Go 语言做项目&#xff0c;有一些 Gitlab 私有仓库需要引用&#xff0c;在做 CI 时&#xff0c;要自行配置权限以获取代码。 最近发现各个项目组在做 CI 遇到仓库权限问题时的解决方式不尽相同&#xff0c;有用 Project Token 的&#xff0c;有用 Deploy K…

unity3d入门教程五

unity3d入门教程五 13鼠标事件处理13.2鼠标跟随13.3鼠标拖拽&#xff08;选中对象&#xff0c;拖动对象&#xff09;13.4几个问题14.1事件函数14.2脚本的执行顺序14.3脚本的参数14.4引用类型的参数&#xff08;进行图片更换&#xff0c;人物换装&#xff09; 13鼠标事件处理 需…

自由流转--实例

一、自由流转的形态 流转能力打破设备界限&#xff0c;多设备联动&#xff0c;使用户应用程序可分可合、可流转&#xff0c;实现如邮件跨设备编辑、多设备协同健身、多屏游戏等分布式业务。 二、跨端迁移 在应用开发层面&#xff0c;跨端迁移指在A端运行的UIAbility迁移到B端上…

Linux操作系统如何添加新字体

在一个Linux操作系统及办公软件刚安装后&#xff0c;会发现缺少常用的“楷体_GB2312”和“仿宋_GB2312”字体。此时&#xff0c;只需要从其它电脑复制到或者从互联网上下载到这两个字体文件&#xff0c;然后导入到自己的电脑即可&#xff0c;再次打开办公软件就会看到这个字体已…

linux重要文件

/etc/sysconfig/network-scripts/ifcfg-eth1 网卡重启 /etc/init.d/network restart ifup ethname & ifdown ethname /etc/resolv.conf 设置Linux本地的客户端DNS的配置文件 linux客户端DNS可以在网卡配置文件(/etc/sysconfig/network/ifcfg-eth0 DNS2)里配置 也可以在/et…

FL Studio 24.1.1.4285中文破解完整版免费下载FL 2024注册密钥完整版crack百度云安装包下载

FL Studio 24.1.1.4285中文破解版是一个强大的软件选项&#xff0c;可以使用专业应用程序&#xff08;如最先进的录音机、均衡器、内置工具等&#xff09;制作循环和歌曲。它由数百个合成器和混响等效果以及均衡器组成&#xff0c;除此之外&#xff0c;您还可以在新音乐制作的方…