【C++篇】红黑树的实现

目录

前言:

一,红黑树的概念

1.1,红黑树的规则

1.2,红黑树的最长路径 

1.3,红黑树的效率分析

 二,红黑树的实现

2.1,红黑树的结构

2.2,红黑树的插入

2.2.1,大致过程 

2.2.2,情况1:变色处理 

 2.2.3,情况2:单旋+变色

2.2.4,情况3:双旋+变色

2.3,红黑树插入代码的实现

2.4,红黑树的验证

三,整体代码

四,测试代码


前言:

本篇会用到上篇【AVL树的实现】中的旋转知识。

一,红黑树的概念

        红黑树是一颗二叉搜索树,它的每一个节点增加一个存储为来表示节点的颜色。可以是红色或者黑色。它通过对从根开始到叶子节点的每条路径上各个节点颜色的约束,确保最长路径不会超过最短路径的2倍,从而实现平衡的。

1.1,红黑树的规则

1,每个节点不是红色就是黑色。

2,根节点是黑色的。

3,红色节点的两个孩子只能是 黑色节点或者是空节点。也就是说不能出现连续的红色节点

4,对于任意一个节点,从该节点开始,到叶子节点的所有路径上,均包含相同数量的黑色节点。

以上 都是红黑树,满足红黑树的规则。

1.2,红黑树的最长路径 

1,由第四条规则可知,从根节点开始的每条路径上,黑色节点的数量相同。所以在极端场景下,一颗红黑树,它的最短路径就是节点全为黑色的路径。假设红黑树的每条路径黑色节点数量都为b,那么最短路径的节点数量为b.

2,由 第三条规则可知,一条路径上不能由连续的红色节点,最长路径是由一黑一红间隔组成的,所以最长路径为2*b。

3,而对于一颗红黑树,最长和最短路径不一定存在。我们可以得出对于任意一颗红黑树,它的任意 一条路径长度x都是,b<=x<=2*b.

1.3,红黑树的效率分析

假设N是红黑树节点的数量,h是最短路径的长度,最长路径不超过2*h

可以得到2^h-1<= N <= 2^(2*h)-1,推出h大致为logN,也就意味着红黑树的增删查该最坏走2*logN,时间复杂度O(logN).

 二,红黑树的实现

2.1,红黑树的结构

enum color
{
    Red,
    Black
};

template<class k,class v>
struct RBTreeNode
{
    RBTreeNode(const pair<k,v>& kv)
        :_left(nullptr)
        ,_right(nullptr)
        ,_parent(nullptr)
        ,_kv(kv)
    {}
    RBTreeNode<k, v>* _left;
    RBTreeNode<k, v>* _right;
    RBTreeNode<k, v>* _parent;
    pair<k, v> _kv;
    color _col;
};

template<class k,class v>
class RBTree
{
    typedef RBTreeNode<k, v> Node;
public:

   //...

private:

    Node* _root=nullptr;
};
 

2.2,红黑树的插入

2.2.1,大致过程 

1,插入一个值需要按照搜索树的规则进行插入,再判断插入后是否满足红黑树的规则。

2,如果是空树插入,新增节点就是黑色节点。如果是非空树插入,新增节点就必须是红色节点,因为如果插入黑色节点,就一定会破坏规则4,而插入红色节点是有可能会破坏规则3,而且对于规则3来说,解决方法比规则4的解决方法容易。

3,非空树插入后,如果父节点是黑色节点,则没有违反任何规则,插入结束。

4,非空树插入后,如果父节点是红色节点,则违反规则3,进一步分析。

2.2.2,情况1:变色处理 

由上图可知,c为红,p为红,g为黑,u存在且为红。在这种情况下,我们需要将p和u变黑,g变红,g成为新的c,继续向上更新。

分析:

        因为p和u都是红色的,g是黑色的。把p和u变黑,左边子树路径各增加一个黑色节点,g再变红,相当于g所在路径的黑色节点数量不变,同时解决了c和p连续红节点的问题。

        需要继续往上跟新是因为g是红色,如果g的父亲还是红色,就需要继续处理;如果g的父亲是黑色,则处理结束;如果g是整棵树的根,再将g变成黑色即可。 

 2.2.3,情况2:单旋+变色

c为红,p为红,u不存在或者u存在且u为黑色。在这种情况下,就需要进行单旋+变色处理

分析:

u不存在时,c一定是新增节点。

u存在且为黑色时,c一定不是新增节点,是在c的子树中插入,符号情况1,经过情况1后,c被调整为红色的。

 上图展示的是右单旋的场景,下面是根据右单旋,画出的左单旋大致图,与右单旋过程相似:

 总结:经过单旋+变色后,我们可以看到p做了子树新的根,且p是黑色的,所以不管p的父亲是黑色的还是红色的,都满足红黑树的规则,所以此时,就不需要往上更新了。

2.2.4,情况3:双旋+变色

c为红,p为红,g为黑,u不存在或者u存在且为黑,u不存在,则c一定是新增结点,u存在且为黑,则 c一定不是新增,c之前是黑色的,是在c的子树中插入,符合情况1,变色将c从黑色变成红色,更新上来的。

 上图展示的是左右双旋+变色,同样右左双旋类似:

同样经过双旋+变色后,c成为新的根,且c为黑色,所以也不需要继续向上更新了。

2.3,红黑树插入代码的实现

bool Insert(const pair<k, v>& kv)
{//插入根节点,color->Blackif (_root == nullptr){_root = new Node(kv);_root->_col = Black;return true;}//根据搜索树的规则查找插入位置Node* cur = _root;Node* parent = nullptr;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;elseparent->_left = cur;cur->_parent = parent;//颜色处理+旋转while (parent&& parent->_col == Red){//gNode* grandfather = parent->_parent;if (parent == grandfather->_left){//p为g的左边//    g//  p   uNode* uncle = grandfather->_right;//叔叔存在且为红,情况1if (uncle && uncle->_col == Red){//变色parent->_col = Black;uncle->_col = Black;grandfather->_col = Red;//继续向上处理cur = grandfather;parent = cur->_parent;}else{//情况2,3//叔叔不存在或者叔叔为黑//u为黑,则c之前是黑的//u不存在,则c是新插入的if (cur == parent->_left){//右单旋场景//   g//  p   u// cRotateR(grandfather);//变色parent->_col = Black;grandfather->_col = Red;}else{//左右双旋场景//    g//  p   u//     cRotateL(parent);RotateR(grandfather);//变色cur->_col = Black;grandfather->_col = Red;}//不需要进行向上更新break;}}else{//p为g的右边//   g// u   pNode* uncle = grandfather->_left;if (uncle && uncle->_col == Red){//情况1//变色parent->_col = Black;uncle->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{//情况2,3if (cur == parent->_right){//左单旋场景//  g// u   p//       cRotateL(grandfather);//变色parent->_col = Black;grandfather->_col = Red;}else{//右左双旋场景//   g// u   p//   cRotateR(parent);RotateL(grandfather);//变色cur->_col = Black;grandfather->_col = Red;}//不需要继续向上更新break;}}}//跟的颜色可能被调整为红色,最后一步改为黑色即可_root->_col = Black;return true;
}
//右单旋
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;if (subLR)subLR->_parent = parent;parent->_left = subLR;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (pparent->_left == parent)pparent->_left = subL;elsepparent->_right = subL;subL->_parent = pparent;}
}
//左单旋
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;if (subRL)subRL->_parent = parent;parent->_parent = subR;subR->_left = parent;if (parent == _root){_root = subR;_root->_parent = nullptr;}else{if (pparent->_left == parent)pparent->_left = subR;elsepparent->_right = subR;subR->_parent = pparent;}
}

2.4,红黑树的验证

我们只需验证形成的树是否满足红黑树的四条规则即可。

其中,规则1和规则 2可以直接检查。

对于规则3,我们可以进行前序遍历,遍历到一个节点,判断该节点的颜色,再判断它的两个孩子的颜色,这样做太麻烦了。我们可以反过来,遍历到一个节点,如果他是红色的,判断它的父亲节点是否为黑色。

对于规则4,我们可以先从根开始找到一条路径上黑色节点的个数refNum,再对整棵树进行前序遍历,用变量 blackNum记录黑色节点的个数,当遍历到空的时候,与refNum比较即可。

bool Check(Node* root, int BlackNum, int num)
{if (root == nullptr){if (BlackNum != num)return false;return true;}if (root->_col == Red && root->_parent->_col == Red)return false;if (root->_col == Black)BlackNum++;return Check(root->_left, BlackNum, num) && Check(root->_right, BlackNum, num);
}
//验证
bool isbalance()
{if (_root == nullptr)return true;if (_root->_col == Red)return false;int num = 0;Node* cur = _root;while (cur){if (cur->_col == Black)num++;cur = cur->_left;}return Check(_root, 0, num);
}

三,整体代码

enum color
{Red,Black
};template<class k,class v>
struct RBTreeNode
{RBTreeNode(const pair<k,v>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv){}RBTreeNode<k, v>* _left;RBTreeNode<k, v>* _right;RBTreeNode<k, v>* _parent;pair<k, v> _kv;color _col;
};template<class k,class v>
class RBTree
{typedef RBTreeNode<k, v> Node;
public:bool Insert(const pair<k, v>& kv){//插入根节点,color->Blackif (_root == nullptr){_root = new Node(kv);_root->_col = Black;return true;}//根据搜索树的规则查找插入位置Node* cur = _root;Node* parent = nullptr;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;elseparent->_left = cur;cur->_parent = parent;//颜色处理+旋转while (parent&& parent->_col == Red){//gNode* grandfather = parent->_parent;if (parent == grandfather->_left){//p为g的左边//    g//  p   uNode* uncle = grandfather->_right;//叔叔存在且为红,情况1if (uncle && uncle->_col == Red){//变色parent->_col = Black;uncle->_col = Black;grandfather->_col = Red;//继续向上处理cur = grandfather;parent = cur->_parent;}else{//情况2,3//叔叔不存在或者叔叔为黑//u为黑,则c之前是黑的//u不存在,则c是新插入的if (cur == parent->_left){//右单旋场景//   g//  p   u// cRotateR(grandfather);//变色parent->_col = Black;grandfather->_col = Red;}else{//左右双旋场景//    g//  p   u//     cRotateL(parent);RotateR(grandfather);//变色cur->_col = Black;grandfather->_col = Red;}//不需要进行向上更新break;}}else{//p为g的右边//   g// u   pNode* uncle = grandfather->_left;if (uncle && uncle->_col == Red){//情况1//变色parent->_col = Black;uncle->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{//情况2,3if (cur == parent->_right){//左单旋场景//  g// u   p//       cRotateL(grandfather);//变色parent->_col = Black;grandfather->_col = Red;}else{//右左双旋场景//   g// u   p//   cRotateR(parent);RotateL(grandfather);//变色cur->_col = Black;grandfather->_col = Red;}//不需要继续向上更新break;}}}//跟的颜色可能被调整为红色,最后一步改为黑色即可_root->_col = Black;return true;}//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;if (subLR)subLR->_parent = parent;parent->_left = subLR;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (pparent->_left == parent)pparent->_left = subL;elsepparent->_right = subL;subL->_parent = pparent;}}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;if (subRL)subRL->_parent = parent;parent->_parent = subR;subR->_left = parent;if (parent == _root){_root = subR;_root->_parent = nullptr;}else{if (pparent->_left == parent)pparent->_left = subR;elsepparent->_right = subR;subR->_parent = pparent;}}void Inorder(){_Inorder(_root);}int Height(){return _Height(_root);}int size(){return _size(_root);}Node* Find(const k& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}//验证bool isbalance(){if (_root == nullptr)return true;if (_root->_col == Red)return false;int num = 0;Node* cur = _root;while (cur){if (cur->_col == Black)num++;cur = cur->_left;}return Check(_root, 0, num);}
private:bool Check(Node* root, int BlackNum, int num){if (root == nullptr){if (BlackNum != num)return false;return true;}if (root->_col == Red && root->_parent->_col == Red)return false;if (root->_col == Black)BlackNum++;return Check(root->_left, BlackNum, num) && Check(root->_right, BlackNum, num);}int _size(Node* root){if (root == nullptr)return 0;return _size(root->_left) + _size(root->_right) + 1;}int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_Inorder(root->_right);}Node* _root=nullptr;
};

四,测试代码

void TestRBTree1()
{RBTree<int, int> t;// 常规的测试用例int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };// 特殊的带有双旋场景的测试用例//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){t.Insert({ e, e });}t.Inorder();cout << t.isbalance() << endl;
}
int main()
{TestRBTree1();return 0;
}

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

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

相关文章

如何在谷歌浏览器中设置自定义安全警告

随着网络环境的日益复杂&#xff0c;浏览器的安全问题也愈发引人关注。谷歌浏览器作为一款广泛使用的浏览器&#xff0c;其自定义安全警告功能为用户提供了更加个性化和安全的浏览体验。本文将详细介绍如何在谷歌浏览器中设置自定义安全警告&#xff0c;帮助用户更好地保护自己…

Spring 6 第1章——概述

一.Spring是什么 Spring是一款主流的Java EE轻量级&#xff08;体积小、不需要依赖其它组件&#xff09;开源框架Spring的目的是用于简化Java企业级应用的开发难度和开发周期Spring的用途不仅限于服务端的开发&#xff0c;从简单性、可测试性和松耦合的角度而言&#xff0c;任…

C语言预处理艺术:编译前的魔法之旅

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文一、预处理的作用与流程&#xf…

基于Springboot + vue实现的旅游网站

&#x1f942;(❁◡❁)您的点赞&#x1f44d;➕评论&#x1f4dd;➕收藏⭐是作者创作的最大动力&#x1f91e; &#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;欢迎留言讨论 &#x1f525;&#x1f525;&…

docker-compose和docker仓库

一、docker-compose 1.概述 docker-compose是一个自动编排工具&#xff0c;可以根据dockerfile自动化部署docker容器。 主要功能 配置定义 使用YAML文件&#xff08;通常命名为docker - compose.yml&#xff09;来描述应用程序的服务、网络和卷等配置。 容器编排 可以同时…

MAC AndroidStudio模拟器无网络

先确认PC端是正常访问网络的&#xff1b; 模拟器端修改Wifi设置&#xff1a;设置 - 网络和互联网 - WALN设置 按照上图修改&#xff1b; IP设置&#xff1a;从DHCP修改为静态&#xff0c;IP地址&#xff1a;10.0.2.16 &#xff0c;网关&#xff1a;10.0.2.2 &#xff0c; DNS…

Wireshark 使用教程:网络分析从入门到精通

一、引言 在网络技术的广阔领域中&#xff0c;网络协议分析是一项至关重要的技能。Wireshark 作为一款开源且功能强大的网络协议分析工具&#xff0c;被广泛应用于网络故障排查、网络安全检测以及网络协议研究等诸多方面。本文将深入且详细地介绍 Wireshark 的使用方法&#x…

Java 面试题 - ArrayList 和 LinkedList 的区别,哪个集合是线程安全的?

Java 面试题 - ArrayList 和 LinkedList 的区别&#xff0c;哪个集合是线程安全的&#xff1f; 在 Java 开发中&#xff0c;ArrayList和LinkedList是两个常用的集合类&#xff0c;它们在数据结构和性能上有诸多不同&#xff0c;同时线程安全性也各有特点。深入理解这些差异&am…

nvim 打造成可用的IDE(2)

上一个 文章写的太长了&#xff0c; 后来再写东西 就一卡一卡的&#xff0c;所以新开一个。 主要是关于 bufferline的。 之前我的界面是这样的。 这个图标很不舒服有。 后来发现是在这里进行配置。 我也不知道&#xff0c;这个配置 我是从哪 抄过来的。 测试结果&#xff1…

升级 SpringBoot3 全项目讲解 — 为什么 SpringBoot3 应该抛弃 Maven,搭配 Gradle 来使用?

学会这款 &#x1f525;全新设计的 Java 脚手架 &#xff0c;从此面试不再怕&#xff01; 随着 Spring Boot 3 的发布&#xff0c;许多开发者开始考虑如何将现有项目升级到最新版本。Spring Boot 3 带来了许多新特性&#xff0c;包括对 Java 17 的支持、更好的性能优化以及对 G…

Java学习笔记(二十三)

1 CacheEvict CacheEvict是Spring框架中用于清空缓存的注解。以下是对CacheEvict注解的详细介绍&#xff1a; 1.1 作用 CacheEvict注解的主要作用是删除缓存中的数据。在方法执行后或执行前&#xff08;根据配置&#xff09;&#xff0c;它可以清空指定的缓存项或整个缓存区…

如何优化Elasticsearch大文档查询?

记录一次业务复杂场景下DSL优化的过程 背景 B端商城业务有一个场景就是客户可见的产品列表是需要N多闸口及各种其它逻辑组合过滤的&#xff0c;各种闸口数据及产品数据都是存储在ES的(有的是独立索引&#xff0c;有的是作为产品属性存储在产品文档上)。 在实际使用的过程中&a…

openCvSharp 计算机视觉图片找茬

一、安装包 <PackageReference Include"OpenCvSharp4" Version"4.10.0.20241108" /> <PackageReference Include"OpenCvSharp4.runtime.win" Version"4.10.0.20241108" /> 二、准备两张图片 三、编写代码 using OpenCv…

实战:FRP内网穿透部署-支持ssh、web访问

目录 1 准备工作2 公网服务器部署server端2.1 frps.ini配置 3 内网客户端部署client端3.1 frpc.ini配置&#xff08;内网服务器01&#xff09;3.2 frpc.ini配置&#xff08;内网服务器02&#xff09; 4 服务启动脚本4.1 公网服务器 server4.2 内网服务器 client 2 systemctl常见…

Uniapp中实现加载更多、下拉刷新、返回顶部功能

一、加载更多&#xff1a; 在到达底部时&#xff0c;将新请求过来的数据追加到原来的数组即可&#xff1a; import {onReachBottom } from "dcloudio/uni-app";const pets ref([]); // 显示数据function network() {uni.request({url: "https://api.thecatap…

C# 多线程 Task TPL任务并行

先总结一下 之前发展过程的要点 1&#xff1a; 为了保证多线程正确顺序执行 线程同步 2&#xff1a; 为了节省操作系统线程资源 线程池 异步 方式管理 正常来讲 使用这俩个要点 进行使用 多线程可以满足开发使用需求 但是 新的问题产生了 那就是 多个异步操作 需要编写大量的代…

C++单例模式的设计

单例模式&#xff08;Singleton Pattern&#xff09;是一种设计模式&#xff0c;用于确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问该实例。在C中&#xff0c;单例模式通常用于管理全局资源或共享状态。 以下是C中实现单例模式的几种常见方式&#xff1a; 懒…

HBASE学习(一)

1.HBASE基础架构&#xff0c; 1.1 参考&#xff1a; HBase集群架构与读写优化&#xff1a;理解核心机制与性能提升-CSDN博客 1.2问题&#xff1a; 1.FLUSH对hbase的影响 2. HLog和memstore的区别 hlog中存储的是操作记录&#xff0c;比如写、删除。而memstor中存储的是写入…

Flutter:封装ActionSheet 操作菜单

演示效果图 action_sheet_util.dart import package:ducafe_ui_core/ducafe_ui_core.dart; import package:flutter/material.dart; import package:demo/common/index.dart;class ActionSheetUtil {/// 底部操作表/// [context] 上下文/// [title] 标题/// [items] 选项列表 …

【Rust练习】28.use and pub

练习题来自&#xff1a;https://practice-zh.course.rs/crate-module/use-pub.html 1 使用 use 可以将两个同名类型引入到当前作用域中&#xff0c;但是别忘了 as 关键字. use std::fmt::Result; use std::io::Result;fn main() {}利用as可以将重名的内容取别名&#xff1a;…