C++手撕红黑树

文章目录

    • 红黑树
      • 概念
      • 性质(条件限制)
      • 节点的定义
      • 红黑树的结构
      • 红黑树的插入
        • cur为红,p为红,g为黑,u存在且为红
        • cur为红,p为红,g为黑,u不存在或u为黑,插入到p对应的一边
        • cur为红,p为红,g为黑,u不存在或u存在且为黑,插入到与p相反的一边
        • 示例代码
      • 红黑树的验证
      • 红黑树与AVL树的比较
    • 完整代码

红黑树

概念

和AVL树一样,红黑树也是一种二叉搜索树,是解决二叉搜索树不平衡的另一种方案,他在每个节点上增加一个存储位,用于表示节点的颜色,是Red或者Black

红黑树的核心思想是通过一些着色的条件限制,达到一种最长路径不超过最短路径的两倍的状态

所以说红黑树并不是严格平衡的树,而是一种近似平衡

例如

 2024-04-08 134510.png

性质(条件限制)

红黑树一共有五条性质,由此来保证最长路径不超过最短路径的两倍

  1. 每个节点都有颜色,不是黑色就是红色
  2. 根节点是黑色的
  3. 如果一共节点是红色,那么他的子节点一定是黑色(不会出现两个红色节点连接的情况)
  4. 对于每个节点,以这个节点到所有后代的任意路径上,均包含相同数目的黑色节点
  5. 每个叶子节点(空节点)是黑色的(为了满足第四条性质,某些情况下如果没有第五条第四条会失效)

节点的定义

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

我们定义颜色时,使用枚举类型,可以方便且明了的看到颜色

除此之外我们默认插入节点是红色的,因为一旦插入节点是黑色,就会违反第四条规则,如果要满足的话,就要走到每一条路径上插入对应的黑色节点,代价巨大

当插入节点是红色时,有可能会违反第三条规则,但是我们可以通过变色,旋转等操作在局部进行改变,这样就能使之仍然满足条件

红黑树的结构

为了后续利用红黑树封装map和set,我们对红黑树增加一个头节点,为了和根节点进行区分,我们将头节点赋为黑色,并且让头节点的parent指向根节点,left指向红黑树的最小节点,right指向最大节点,如图

image.png

红黑树的插入

红黑树插入时也是按照二叉搜索树的规则进行插入,并在此基础上加上平衡条件,因此插入也就分为两步

  1. 按照二叉搜索树的规则插入新节点
  2. 插入节点后检测规则是否被破坏

因为插入红节点时只有可能破坏第三条规则,因此我们只需要判断父节点是否为红色即可

然后我们分情况讨论

为了方便叙述,我们约定cur为插入节点,p为父节点,g为祖父节点,u为叔叔节点

cur为红,p为红,g为黑,u存在且为红

画出来是这样的

image.png

这时我们需要将g改为红色,p和u改为黑色即可,这样既能保证红色不连续,黑色数量一致,如图

image.png

但是如果g是是子树,那么g一定有父节点,当g的父节点也是红色时,也就同样需要向上调整了

cur为红,p为红,g为黑,u不存在或u为黑,插入到p对应的一边

画出来是这样的

image.png

u的情况有两种

  1. u节点不存在,说明cur一定是新插入的节点,因为要保证左右两个路径的黑色节点的数量相同
  2. u节点存在,说明cur节点是由下至上调整的红色,原因也是左右路径的黑色节点要相同

对于这两种情况的调整方法是相同的,如果p是g的左节点,cur为p的左节点,则右单旋,如果p是g的右节点,cur为p的右节点,则左单旋

同时p要变成黑色,g要变成红色

变成如下状态

image.png

那么因为最上面的根节点颜色没有变化,也就不需要继续向上调整了

cur为红,p为红,g为黑,u不存在或u存在且为黑,插入到与p相反的一边

如图

image.png

这种情况需要针对p进行单旋,如果p为g的左节点,cur为p的右节点,则对p左单旋,反之则为右单旋,此时就会变成第二种情况,再继续处理即可

第一次处理的结果如下

image.png

示例代码
template<class K, class T, class KeyOfT>
class RBTree {typedef RBTreeNode<T> Node;
public:pair<Node*, bool> Insert(const T& data) {// 插入根节点直接返回if (_root == nullptr) {_root = new Node(data);_root->_col = BLACK;return make_pair(_root, true);}Node* parent = nullptr;Node* cur = _root;KeyOfT kot;// 平衡二叉树找到插入位置while (cur) {if (kot(cur->_data) < kot(data)) {parent = cur;cur = cur->_right;} else if (kot(cur->_data) > kot(data)) {parent = cur;cur = cur->_left;} else {return make_pair(cur, false);}}// 新建节点cur = new Node(data);Node* newnode = cur;cur->_col = RED;// 连接父节点if (kot(parent->_data) < kot(data)) {parent->_right = cur;cur->_parent = parent;} else {parent->_left = cur;cur->_parent = parent;}// 如果父节点存在且父节点为红色则需要调整while (parent && parent->_col == RED) {Node* grandfather = parent->_parent;if (parent == grandfather->_left) {//     g//   p   u// c // 判断u是否存在和他的颜色Node* uncle = grandfather->_right;// 如果存在且为红色if (uncle && uncle->_col == RED) {// 变色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 向上调整cur = grandfather;parent = cur->_parent;} else {// 如果不存在或u为黑色,需要判断同侧还是异侧// 如果是同侧if (cur == parent->_left) {//     g//   p// cRotateR(grandfather); // 右旋// 调整颜色parent->_col = BLACK;grandfather->_col = RED;} else {//    g//  p//    cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}} else { // p = g->rNode* uncle = grandfather->_left;//     g//   u   p//         c// 判断u是否存在和他的颜色// 如果存在且为红色if (uncle && uncle->_col == RED) {// 变色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 向上调整cur = grandfather;parent = cur->_parent;} else {if (cur == parent->_right) {RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;} else {//     g//   u   p //     c//RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(newnode, true);}void RotateL(Node* parent) {Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;Node* parentParent = parent->_parent;parent->_parent = subR;if (subRL)subRL->_parent = parent;else {if (parentParent->_left == parent) {parentParent->_left = subR;} else {parentParent->_right = subR;}subR->_parent = parentParent;}}void RotateR(Node* parent) {Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (_root == parent) {_root = subL;subL->_parent = nullptr;} else {if (parentParent->_left == parent) {parentParent->_left = subL;} else {parentParent->_right = subL;}subL->_parent = parentParent;}}
private:Node* _root = nullptr;
};

红黑树的验证

红黑树要验证需要验证两个部分

  1. 检测是否中序遍历是有序序列
  2. 检测是否满足红黑树的性质

这里我们就不讲红黑树的删除了,完成红黑树的验证之后就算作已经完成了任务,接下来会使用红黑树模拟实现map和set

红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,但是红黑树不追求绝对的平衡,降低了插入和旋转的次数,因此性能比AVL更优,而且红黑树比AVL树的实现更加简单,所以实际中运用红黑树更多

完整代码

#pragma once
#include<utility>
#include<iostream>
using namespace std;
// 颜色
enum Color {RED,BLACK
};template<class T>
struct RBTreeNode {RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Color _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED) {}
};template<class K, class T, class KeyOfT>
class RBTree {typedef RBTreeNode<T> Node;
public:pair<Node*, bool> Insert(const T& data) {if (_root == nullptr) {_root = new Node(data);_root->_col = BLACK;return make_pair(_root, true);}Node* parent = nullptr;Node* cur = _root;KeyOfT kot;// 平衡二叉树找到插入位置while (cur) {if (kot(cur->_data) < kot(data)) {parent = cur;cur = cur->_right;} else if (kot(cur->_data) > kot(data)) {parent = cur;cur = cur->_left;} else {return make_pair(cur, false);}}// 新建节点cur = new Node(data);Node* newnode = cur;cur->_col = RED;// 连接父节点if (kot(parent->_data) < kot(data)) {parent->_right = cur;cur->_parent = parent;} else {parent->_left = cur;cur->_parent = parent;}// 如果父节点存在且父节点为红色则需要调整while (parent && parent->_col == RED) {Node* grandfather = parent->_parent;if (parent == grandfather->_left) {//     g//   p   u// c // 判断u是否存在和他的颜色Node* uncle = grandfather->_right;// 如果存在且为红色if (uncle && uncle->_col == RED) {// 变色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 向上调整cur = grandfather;parent = cur->_parent;} else {// 如果不存在或u为黑色,需要判断同侧还是异侧// 如果是同侧if (cur == parent->_left) {//     g//   p// cRotateR(grandfather); // 右旋// 调整颜色parent->_col = BLACK;grandfather->_col = RED;} else {//    g//  p//    cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}} else { // p = g->rNode* uncle = grandfather->_left;//     g//   u   p//         c// 判断u是否存在和他的颜色// 如果存在且为红色if (uncle && uncle->_col == RED) {// 变色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 向上调整cur = grandfather;parent = cur->_parent;} else {if (cur == parent->_right) {RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;} else {//     g//   u   p //     c//RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(newnode, true);}void RotateL(Node* parent) {Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;Node* parentParent = parent->_parent;parent->_parent = subR;if (subRL)subRL->_parent = parent;else {if (parentParent->_left == parent) {parentParent->_left = subR;} else {parentParent->_right = subR;}subR->_parent = parentParent;}}void RotateR(Node* parent) {Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (_root == parent) {_root = subL;subL->_parent = nullptr;} else {if (parentParent->_left == parent) {parentParent->_left = subL;} else {parentParent->_right = subL;}subL->_parent = parentParent;}}void InOrder() {_InOrder(_root);cout << endl;}void _InOrder(Node* root) {if (root == nullptr)return;_InOrder(root->_left);cout << root->_data << ' ';_InOrder(root->_right);}bool Check(Node* root, int blacknum, const int refVal) {if (root == nullptr) {if (blacknum != refVal) {cout << "存在黑色节点数量不相等的路径" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED) {cout << "存在连续的红节点" << endl;return false;}if (root->_col == BLACK) {++blacknum;}return Check(root->_left, blacknum, refVal) && Check(root->_right, blacknum, refVal);}bool IsBalance() {if (_root == nullptr)return true;if (_root->_col == RED)return false;int refVal = 0; // 参考值Node* cur = _root;while (cur) {if (cur->_col == BLACK) {++refVal;}cur = cur->_left;}int blacknum = 0;return Check(_root, blacknum, refVal);}int Height() {return _Height(_root);}int _Height(Node* root) {if (root == nullptr)return 0;int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH + rightH;}size_t Size() {return _Size(_root);}size_t _Size(Node* root) {if (root == nullptr)return 0;return _Size(root->_left) + _Size(root->_right) + 1;}Node* Find(const K& key) {Node* cur = _root;while (cur) {if (cur->_data < key) {cur = cur->_right;}else if (cur->_data > key) {cur = cur->_left;}else {return cur;}}return nullptr;}private:Node* _root = nullptr;
};

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

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

相关文章

02 _ 分布式系统的指标:啥是分布式的三围

你好&#xff0c;我是聂鹏程。 在上一篇文章中&#xff0c;通过对分布式发展历程的学习&#xff0c;我们对分布式技术有了一个整体印象。接下来&#xff0c;我们就再来看看可以用哪些指标去具体地衡量一个分布式系统。如果你已经对分布式系统的指标了解得很清楚了&#xff0c;…

群晖NAS使用Docker部署Potopea在线图片编辑工具并实现公网访问

文章目录 1. 部署Photopea2. 运行Photopea3. 群晖安装Cpolar4. 配置公网地址5. 公网访问测试6. 固定公网地址 本文主要介绍如何在群晖NAS使用Docker部署Potopea在线图片编辑工具&#xff0c;并结合cpolar内网穿透实现公网环境可以远程访问本地部署的Potopea. Photopea是一款强大…

第十五届蓝桥杯测试组模拟赛两期

文章目录 功能测试一期-场景法-登录功能一期-等价类-边界值-添加用户账号输入框一期-登录-缺陷报告一期- UI自动化测试一期-单元测试-路径覆盖二期-正交法-搜索条件组合二期-测试用例二期-缺陷报告二期-自动化测试二期-单元测试-基本路径覆盖 功能测试 一期-场景法-登录功能 …

【云呐】工单管理流程,工单管理怎么处理

工单创建  客户或内部员工在系统中创建工单。工单应包括以下信息&#xff1a;  问题的描述  工单的优先级和紧急程度  相关的客户或内部员工信息  工单的类型或类别  相关的附件或文件 工单分配  工单需要分配给适当的人员或团队来解决。分配过程可能涉及到以下步…

MySQL 优化总结

目标知识 MySQL执行流程图 MySQL 优化成本路线图 优化成本&#xff1a;硬件>系统配置>数据库表结构>SQL及索引。优化效果&#xff1a;硬件<系统配置<数据库表结构<SQL及索引。 MySQL 五大优化原则 减少数据返回&#xff1a;设置合理字段数据类型、启用压缩…

Vue-B站学习笔记

1. 路由配置 B站视频之Vue route文件下的index.js app.vue

js获取上周本周下周的日期(附Demo)

目录 前言1. 基本知识2. Demo3. 彩蛋 前言 现在的时间点是&#xff1a;2024-04-08&#xff0c;对应的日期如下&#xff08;上周、这周、下周&#xff09; 1. 基本知识 讲述Demo之前&#xff0c;先补充一些基础知识 JavaScript 中的 Date 对象是用于处理日期和时间的对象。它…

提问:一台交换机能带动多少个网络监控摄像头?

你们好&#xff0c;我的网工朋友。 标题的这个问题呢&#xff0c;其实有点问题的。因为这一个问题&#xff0c;需要有一堆条件加上&#xff0c;才好判断。 比如&#xff0c;你的交换机是百兆的还是千兆的&#xff1f; 整机的POE功率和端口POE功率是多少&#xff1f; 交换机…

【C语言】青蛙跳台阶问题

题目&#xff1a;一只青蛙一次可以跳上1级台阶&#xff0c;也可以跳上2级台阶。现求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 题目分析&#xff1a; 当 n 等于 1 时&#xff0c;青蛙只能跳一级台阶到达&#xff0c;因此只有一种跳法&#xff0c;直接返回 1。当 n 等于 2 时…

【Android】App通信基础架构相关类源码解析

应用通信基础架构相关类源码解析 这里主要对Android App开发时&#xff0c;常用到的一些通信基础类进行一下源码的简单分析&#xff0c;包括&#xff1a; Handler&#xff1a;处理器&#xff0c;与某个Looper&#xff08;一个线程对应一个Looper&#xff09;进行关联。用于接…

最少按键次数

题目描述 给你一个字符串 s&#xff0c;由小写英文字母组成。 电话键盘上的按键与 不同 小写英文字母集合相映射&#xff0c;可以通过按压按键来组成单词。例如&#xff0c;按键 2 对应 ["a","b","c"]&#xff0c;我们需要按一次键来输入 &quo…

【javaWeb 原理篇】底层实现原理(快速学习配置原理,Bean管理)

Spring底层 配置优先级Bean管理获取beanBean的作用域第三方Bean SpringBoot原理起步依赖自动配置自动配置的原理自定义starter 配置优先级 Spring中的配置文件如果配置了相同的内容则根据配置优先级进行配置: application.properties>application.yml>application.yaml …

用Python+OpenCV截取视频中所有含有字幕的画面

1、需求背景 有的视频文件的字幕已经压制到了视频的图像中&#xff0c;不能单独提取出字幕文件。网上的 “提取视频字幕” 网站多为提取视频中的字幕文件&#xff0c;而非识别视频图像中的字幕。少数通过OCR技术识别画面中字幕的工具需要在线运行、运行速度较慢&#xff0c;或…

蓝桥杯练习笔记(十八)

蓝桥杯练习笔记&#xff08;十八&#xff09; 一、用辅助栈来优化递归深度过大的问题 输入示例 0000100010000001101010101001001100000011 0101111001111101110111100000101010011111 1000010000011101010110000000001011010100 0110101010110000000101100100000101001001 0…

QT打包生成.exe可执行文件

QT打包生成.exe可执行文件 程序运行图标如何设置快捷方式显示图标QT打包成可执行文件将可执行文件打包成安装包程序运行图标 如何生成如下图标? 首先将你的图标(ico文件)放入当前工程目录,即含有.pro文件的同级目录 右击项目,选择ADD New,选择Qt Resource File, 这是一个…

吴恩达2022机器学习专项课程(一) 5.7 检测梯度下降是否收敛

问题预览/关键词 什么是梯度下降收敛&#xff1f;哪些方法可以检测梯度下降是否收敛&#xff1f;什么是学习曲线&#xff1f;曲线上升代表什么&#xff1f;什么原因造成的&#xff1f;如何检测梯度下降是否收敛&#xff1f;多少次迭代&#xff0c;梯度下降会收敛&#xff1f;什…

C++:初步接触C++(2)

hello&#xff0c;各位小伙伴&#xff0c;本篇文章跟大家一起学习C&#xff0c;感谢大家对我上一篇的支持&#xff0c;如有什么问题&#xff0c;还请多多指教 &#xff01; 文章目录 内联函数1.概念2.特性 auto关键字1.auto简介2.auto的使用细则3.auto不能推导的场景 基于范围…

RTThread studio 驱动开发

rtthread 驱动使用的两种情况 rtthread studio 自动生成 由 RT Thread Studio 自动生成&#xff0c;无需修改任何文件或者简单定义几个宏即可直接使用的驱动&#xff0c;如 GPIO&#xff0c;UART&#xff0c;I2C&#xff0c;SPI&#xff0c;SDIO 和 ETH 等。 使用 RT-Thread S…

如何定位和优化程序CPU、内存等性能之巅

如何定位和优化程序CPU、内存等性能之巅 摘要 性能优化指在不影响系统运行正确性的前提下&#xff0c;使之运行得更快&#xff0c;完成特定功能所需的时间更短&#xff0c;或拥有更强大的服务能力。本文将介绍性能优化的基本概念以及如何定位和优化程序中的CPU、内存和IO瓶颈…

信息泄露漏洞的JS整改方案

引言 &#x1f6e1;️ 日常工作中&#xff0c;我们经常会面临线上环境被第三方安全厂商扫描出JS信息泄露漏洞的情况&#xff0c;这给我们的系统安全带来了潜在威胁。但幸运的是&#xff0c;对于这类漏洞的整改并不复杂。本文将介绍几种可行的整改方法&#xff0c;以及其中一种…