【数据结构高阶】红黑树

目录

一、红黑树的概念

二、红黑树的性质

2.1 红黑树与AVL树的比较

三、红黑树的实现

3.1 红黑树节点的定义

3.2 数据的插入

3.2.1 红黑树的调整思路

3.2.1.1 cur为红,f为红,g为黑,u存在且为红

3.2.1.2 cur为红,f为红,g为黑,u不存在/u存在且为黑

3.2.1.2.1 g、f、cur构成一条直线

3.2.1.2.2 g、f、cur构成一条折线

3.2.2 调整部分的代码实现

3.3 红黑树的验证

3.4 测试代码

四、红黑树实现完整代码


一、红黑树的概念

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

二、红黑树的性质

● 每个结点不是红色就是黑色

● 根节点是黑色的

● 如果一个节点是红色的,则它的两个孩子结点是黑色的(不允许出现连续的红色节点)

● 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(从根节点到每个叶子节点的空孩子路径上有相同数目的黑色节点)

● 每个叶子结点的空孩子节点都是黑色的

根据上述的五条性质,我们可以发现一个红黑树中如果有N个黑色节点,则根节点到任意一个叶子节点的距离:最短为㏒⑵N,最长为2㏒⑵N

2.1 红黑树与AVL树的比较

我们来看到下面的红黑树:

对于这棵红黑树,如果将其看成一个AVL树,是需要进行旋转的,但是在红黑树结构中却不需要

所以红黑树是近似平衡的,在搜索效率上会略逊AVL树一些,但是红黑树在结构上不要求绝对的平衡,这就造成插入相同的数据红黑树翻转的次数少于AVL树

实际使用中,在经常进行增删的场景下红黑树性能比AVL树更优,并且红黑树实现比较简单,所以实际运用中红黑树更多

三、红黑树的实现

3.1 红黑树节点的定义

enum Colour
{RED,BLACK
};template<class Key, class Val>
struct RBTreeNode
{RBTreeNode<Key, Val>* _left;RBTreeNode<Key, Val>* _right;RBTreeNode<Key, Val>* _parent;//多一个指针指向其父节点,方便我们的后续操作pair<Key, Val> _kv;Colour _col;//颜色标识RBTreeNode(const pair<Key, Val>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED)//构造时,优先将节点的颜色置为红色{}
};

在这里提一下为什么要默认将节点的颜色置为红色:

在我们向红黑树中插入一个新节点时,如果将该节点置为黑色,就肯定会影响红黑树性质中的第四条:对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点

例如:

我们现在在上面这棵树中,不管在哪个叶子节点的下方插入一个黑色的新增节点,从根节点到插入的节点的空孩子的路径上的黑色节点数目会变为4,而从根节点到其他叶子节点的空孩子的路径上的黑色节点数目会都为3

所以我们将新增节点的颜色置为红色就一定不会违反第四条红黑树性质,但是第三条呢?如果插入节点的父节点是红色的怎么办?

怎么办我们后面再说,反正总归比置为黑色一定会违反第四条性质好吧

3.2 数据的插入

由于红黑树也是平衡二叉搜索树的一种,我们在插入数据时也要找到合适的位置进行插入:

template<class Key, class Val>
class RBTree
{typedef RBTreeNode<Key, Val> Node;
public:bool Insert(const pair<Key,Val>& kv){Node* cur = _root, * parent = nullptr;while (cur)//找到合适的位置{if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{cout << "插入的值重复" << endl;return false;}}cur = new Node(kv);cur->_parent = parent;//将插入的节点连接上二叉树if (parent == nullptr){_root = cur;}else if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}return true;}private:Node* _root = nullptr;
};

插入到合适的位置之后,我们还要检查是否破坏了红黑树的结构(由于我们插入的是红色节点,所以只会出现两个红色节点连续的情况),如果出现了该情况,我们就要对其进行分类讨论并解决:

3.2.1 红黑树的调整思路

下面我们将出现异常情况的两个节点的那个子节点叫做cur,cur的父节点叫做father(简称f),father的父节点叫做grandfather(简称g),father的兄弟节点叫做uncle(简称u)

例如:

接下来,我们分类讨论:

3.2.1.1 cur为红,f为红,g为黑,u存在且为红

下面画出的情况表示的是抽象出的情况:A、B、C、D、E都是满足构成红黑树的子树

对于这种情况我们先将f和u节点变黑,再将g节点变红即可:

调整完后,要记得再向上检查g节点的父节点是否为红色哦~(如果g节点为整棵红黑树的根,最后要将其颜色置为黑)

3.2.1.2 cur为红,f为红,g为黑,u不存在/u存在且为黑
3.2.1.2.1 g、f、cur构成一条直线

对于这种情况:若f为g的左孩子,cur为f的左孩子,则进行右单旋:

再将f变黑,g变红:

相反, f为g的右孩子,cur为f的右孩子,则进行左单旋转:

再将f变黑,g变红:

3.2.1.2.2 g、f、cur构成一条折线

f为g的左孩子,cur为f的右孩子,则做左右双旋,旋转完后将cur节点颜色置黑、g节点颜色置红:

相反, f为g的右孩子,cur为f的左孩子,则做右左双旋,旋转完后将cur节点颜色置黑、g节点颜色置红:

对于旋转操作还不熟悉的同学可以看到这里:【数据结构高阶】AVL树

3.2.2 调整部分的代码实现

template<class Key, class Val>
class RBTree
{typedef RBTreeNode<Key, Val> Node;
public:bool Insert(const pair<Key, Val>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root, * parent = nullptr;while (cur)//找到合适的位置{if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{cout << "插入的值重复" << endl;return false;}}cur = new Node(kv);//将插入的节点连接上二叉树if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//开始调整while (parent && parent->_col == RED)//红黑树的结构出现两个连续的红色节点{Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED)//cur为红,p为红,g为黑,u存在且为红{parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上更新cur = grandfather;parent = cur->_parent;}elsecur为红,p为红,g为黑,u不存在/u存在且为黑{if (cur == parent->_left)//cur在p的左边,p也在g的左边,构成一条直线{//右单旋RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else//cur在p的右边,p在g的左边,构成一条折线{//左右双旋RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//调整完跳出}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED)//cur为红,p为红,g为黑,u存在且为红{parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上更新cur = grandfather;parent = cur->_parent;}elsecur为红,p为红,g为黑,u不存在/u存在且为黑{if (cur == parent->_right)//cur在p的右边,p也在g的右边,构成一条直线{//左单旋RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else//cur在p的左边,p在g的右边,构成一条折线{//右左双旋RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//调整完跳出}}}_root->_col = BLACK;//确保即便进行过调整后根节点颜色为黑return true;
}private:void RotateL(Node* parent)//左单旋{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;//更新parent的右节点if (subRL)//防止该节点为空{subRL->_parent = parent;//更新subRL的父节点}parent->_parent = subR;//更新parent的父节点subR->_left = parent;//subR的左子树置为parentsubR->_parent = pparent;//更新subR的父节点if (pparent == nullptr)//旋转的是整棵树{_root = subR;//更新根节点}else//将旋转后的子树链接上整个二叉树{if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}}}void RotateR(Node* parent)//右单旋{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;parent->_left = subLR;//更新parent的左节点if (subLR)//防止该节点为空{subLR->_parent = parent;//更新subLR的父节点}parent->_parent = subL;//更新parent的父节点subL->_right = parent;//subL的右子树置为parentsubL->_parent = pparent;//更新subL的父节点if (pparent == nullptr)//旋转的是整棵树{_root = subL;//更新根节点}else//将旋转后的子树链接上整个二叉树{if (pparent->_left == parent){pparent->_left = subL;}else{pparent->_right = subL;}}}private:Node* _root = nullptr;
};

3.3 红黑树的验证

下面我们来写段代码来验证一课树是不是红黑树:

template<class Key, class Val>
class RBTree
{typedef RBTreeNode<Key, Val> Node;
public:bool IsBalance(){if (_root && _root->_col == RED){cout << "根节点颜色是红色" << endl;return false;}int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++benchmark;cur = cur->_left;}// 连续红色节点return _Check(_root, 0, benchmark);}private:bool _Check(Node* root, int blackNum, int benchmark)//计算每条路径上黑色节点的个数{if (root == nullptr){if (benchmark != blackNum){cout << "某条路径黑色节点的数量不相等" << endl;return false;}return true;}if (root->_col == BLACK){++blackNum;}if (root->_col == RED&& root->_parent&& root->_parent->_col == RED){cout << "存在连续的红色节点" << endl;return false;}return _Check(root->_left, blackNum, benchmark)&& _Check(root->_right, blackNum, benchmark);}private:Node* _root = nullptr;
};

3.4 测试代码

void Test_RBTree()
{const size_t N = 50000;RBTree<int, int> t;for (size_t i = 0; i < N; ++i){size_t x = rand() + i;t.Insert(make_pair(x, x));}t.InOrder();cout << t.IsBalance();
}int main()
{Test_RBTree();return 0;
}

测试效果:

四、红黑树实现完整代码

#include<iostream>using namespace std;enum Colour
{RED,BLACK
};template<class Key, class Val>
struct RBTreeNode
{RBTreeNode<Key, Val>* _left;RBTreeNode<Key, Val>* _right;RBTreeNode<Key, Val>* _parent;//多一个指针指向其父节点,方便我们的后续操作pair<Key, Val> _kv;Colour _col;//颜色标识RBTreeNode(const pair<Key, Val>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED)//默认构造时,优先将节点的颜色置为红色{}
};template<class Key, class Val>
class RBTree
{typedef RBTreeNode<Key, Val> Node;
public:bool Insert(const pair<Key, Val>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root, * parent = nullptr;while (cur)//找到合适的位置{if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{cout << "插入的值重复" << endl;return false;}}cur = new Node(kv);//将插入的节点连接上二叉树if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//开始调整while (parent && parent->_col == RED)//红黑树的结构出现两个连续的红色节点{Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED)//cur为红,p为红,g为黑,u存在且为红{parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上更新cur = grandfather;parent = cur->_parent;}elsecur为红,p为红,g为黑,u不存在/u存在且为黑{if (cur == parent->_left)//cur在p的左边,p也在g的左边,构成一条直线{//右单旋RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else//cur在p的右边,p在g的左边,构成一条折线{//左右双旋RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//调整完跳出}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED)//cur为红,p为红,g为黑,u存在且为红{parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上更新cur = grandfather;parent = cur->_parent;}elsecur为红,p为红,g为黑,u不存在/u存在且为黑{if (cur == parent->_right)//cur在p的右边,p也在g的右边,构成一条直线{//左单旋RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else//cur在p的左边,p在g的右边,构成一条折线{//右左双旋RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//调整完跳出}}}_root->_col = BLACK;//确保即便进行过调整后根节点颜色为黑return true;
}void InOrder()//中序遍历{_InOrder(_root);cout << endl;}bool IsBalance(){if (_root && _root->_col == RED){cout << "根节点颜色是红色" << endl;return false;}int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++benchmark;cur = cur->_left;}// 连续红色节点return _Check(_root, 0, benchmark);}private:void RotateL(Node* parent)//左单旋{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;//更新parent的右节点if (subRL)//防止该节点为空{subRL->_parent = parent;//更新subRL的父节点}parent->_parent = subR;//更新parent的父节点subR->_left = parent;//subR的左子树置为parentsubR->_parent = pparent;//更新subR的父节点if (pparent == nullptr)//旋转的是整棵树{_root = subR;//更新根节点}else//将旋转后的子树链接上整个二叉树{if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}}}void RotateR(Node* parent)//右单旋{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;parent->_left = subLR;//更新parent的左节点if (subLR)//防止该节点为空{subLR->_parent = parent;//更新subLR的父节点}parent->_parent = subL;//更新parent的父节点subL->_right = parent;//subL的右子树置为parentsubL->_parent = pparent;//更新subL的父节点if (pparent == nullptr)//旋转的是整棵树{_root = subL;//更新根节点}else//将旋转后的子树链接上整个二叉树{if (pparent->_left == parent){pparent->_left = subL;}else{pparent->_right = subL;}}}void _InOrder(Node* root){if (root == NULL)//如果是空树就直接结束{return;}_InOrder(root->_left);//先递归遍历其左子树cout << root->_kv.first << " ";//再遍历其根节点_InOrder(root->_right);//最后递归遍历其右子树}bool _Check(Node* root, int blackNum, int benchmark){if (root == nullptr){if (benchmark != blackNum){cout << "某条路径黑色节点的数量不相等" << endl;return false;}return true;}if (root->_col == BLACK){++blackNum;}if (root->_col == RED&& root->_parent&& root->_parent->_col == RED){cout << "存在连续的红色节点" << endl;return false;}return _Check(root->_left, blackNum, benchmark)&& _Check(root->_right, blackNum, benchmark);}private:Node* _root = nullptr;
};

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

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

相关文章

uniapp iOS离线打包——运行项目到模拟器报错?

运行项目、打包时报错问题 记录个人在开发过程中遇到的相关问题&#xff0c;后续有时间会不定时更新 文章目录 运行项目、打包时报错问题运行到模拟器报错解决方案 打包报错解决方案 运行到模拟器报错 解决方案 选中项目工程 —> Build Settings 滑动底部 —> User-Defi…

【PyTorch】卷积神经网络

文章目录 1. 理论介绍1.1. 从全连接层到卷积层1.1.1. 背景1.1.2. 从全连接层推导出卷积层 1.2. 卷积层1.2.1. 图像卷积1.2.2. 填充和步幅1.2.3. 多通道 1.3. 池化层&#xff08;又称汇聚层&#xff09;1.3.1. 背景1.3.2. 池化运算1.3.3. 填充和步幅1.3.4. 多通道 1.4. 卷积神经…

【Hive】启动beeline连接hive报错解决

1、解决报错2、在datagrip上连接hive 1、解决报错 刚开始一直报错&#xff1a;启动不起来 hive-site.xml需要配置hiveserver2相关的 在hive-site.xml文件中添加如下配置信息 <!-- 指定hiveserver2连接的host --> <property><name>hive.server2.thrift.bin…

基于SpringBoot+JSP+Mysql宠物领养网站+协同过滤算法推荐宠物(Java毕业设计)

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…

gitbash下载安装

参考教程 零、下载 官网地址 2.43.0win64 链接&#xff1a;https://pan.baidu.com/s/16urs_nmky7j20-qNzUTTkg 提取码&#xff1a;7jaq 一、安装 图标组件&#xff08;Additional icons&#xff09;&#xff1a;选择是否创建桌面快捷方式&#xff1b;桌面浏览&#xff08;Win…

漏刻有时百度地图API实战开发(8)关键词输入检索获取经纬度坐标和地址

在百度地图中进行关键词输入检索时&#xff1a; 在地图页面顶部的搜索框中输入关键词。点击搜索按钮或按下回车键进行搜索。地图将显示与关键词相关的地点、商家、景点等信息。可以使用筛选和排序功能来缩小搜索范围或更改搜索结果的排序方式。点击搜索结果中的地点或商家&…

oops-framework框架 之 日志管理(七)

引擎&#xff1a; CocosCreator 3.8.0 环境&#xff1a; Mac Gitee: oops-game-kit 注&#xff1a; 作者dgflash的oops-framework框架QQ群&#xff1a; 628575875 Logger 作者dgflash针对于oops-framework封装的日志管理类是通过Logger实现的&#xff0c;接口在框架中的定义&…

docker 一键寻找容器在服务器存储位置

docker ps -a找到容器id/容器名称 docker inspect 容器id/容器名称 | grep UpperDir找出该容器在物理机的位置 inspect作用:查看docker详细信息 cd到UpperDir所指向的地址&#xff0c;找到配置文件并修改,到这后,这个位置和你用exec命令进入容器内看到文件是一致的

vue3中关于echars的使用

今天介绍一个好用的插件echars&#xff0c;一个可视化插件Apache ECharts 一、使用步骤 1、安装 npm install echarts --save 2、导入 import * as echarts from echarts 3、正式使用 echars的使用非常的简单&#xff0c;直接点击官网有现成的代码的可用 代码示例 <t…

分层网络模型(OSI、TCP/IP)及对应的网络协议

OSI七层网络模型 OSI&#xff08;Open System Interconnect&#xff09;&#xff0c;即开放式系统互连参考模型&#xff0c; 一般都叫OSI参考模型&#xff0c;是ISO组织于1985年研究的网络互连模型。OSI是分层的体系结构&#xff0c;每一层是一个模块&#xff0c;用于完成某种功…

Java网络编程——安全网络通信

在网络上&#xff0c;信息在由源主机到目标主机的传输过程中会经过其他计算机。在一般情况下&#xff0c;中间的计算机不会监听路过的信息。但在使用网上银行或者进行信用卡交易时&#xff0c;网络上的信息有可能被非法分子监听&#xff0c;从而导致个人隐私的泄露。由于Intern…

Windows 和 MacOS 上安装配置ADB(安卓调试桥)

一、Android 调试桥 (ADB) Android 调试桥&#xff08;ADB&#xff09; 是一款多功能命令行工具&#xff0c;它让你能够更便捷地访问和管理 Android 设备。使用 ADB 命令&#xff0c;你可以轻松执行以下操作 在设备上安装、复制和删除文件&#xff1b;安装应用程序&#xff1…