红黑树——原理刨析

2023-11-06 13:45
文章标签 原理 红黑树 刨析

本文主要是介绍红黑树——原理刨析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        众所周知,红黑树是从AVLTree树中衍变而来的,所以在学红黑树之前还是要好好的理解一下AVLTree树的原理,为理解红黑树减轻理解负担,好了进入正题。

红黑树原理:

        由名可知,红黑树——肯定是与颜色有关的一个树,又因为是从AVLTree树中衍化过来的,所以也是搜索树(不是平衡二叉树,后面讲解定义时会详细解释),通过对不同情况的处理,去调整红黑树节点的颜色或者红黑树的高度去使其满足,红黑树的定义规则。

红黑树的定义:

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

如上图,就是红黑树。

红黑树的性质:

        1. 每个结点不是红色就是黑色
        2. 根节点是黑色的
        3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
        4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
        5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

答:上述红黑树的性质第4条 说明每条路上面的黑色节点数量都是相等的,所以说该节点的左右子树可以有一棵子树全为黑色节点 另一个红黑节点交替(红节点数量与黑节点数量相等),这就能保证其最长路径中节点个数不会超过最短路径节点个数的两倍。

红黑树中重要的函数讲解:

        和AVLTree一样,插入函数是难点,但是掌握AVLTree之后,这里的插入就不怎么难了,AVLTree中提到左旋,右旋,这里不做讲解,如有疑惑,参考上篇文章,有流程图

        在我看来红黑树与AVLTree不同点就是规则不同,红黑树是靠颜色去调整高度差,而AVLTree是通过平衡因子去调节的。

情况一:整棵树或者子树为上上图,就只能进行颜色更新 将uncle更新为黑色 父亲更新为黑色 祖父更新为红色 再继续向上以同样的方式更新 直到更新到根节点或者进行了一次旋转调整 (旋转调整会将树的高度改变并将颜色确定为最终的颜色)就不再向上更新

情况二:uncle存在且为黑或者不存在

1,先进行情况一的颜色更新,出现了旋转的情况,再进行旋转 最后进行旋转的颜色更新

               

        2,刚开始整棵树就为要旋转的情况或者为整棵树的子树,如上图

单选和双旋在AVLTree中已经讲解过了,这里最重要的就是如何进行颜色更新,而不是旋转。

        

红黑树完整代码:

#pragma once
#include<iostream>
using namespace std;enum color
{BLACK,RED
};template<class K,class V>
struct RBTreeNode
{RBTreeNode<K, V>* _parent;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;pair<K, V> _kv;color _col;RBTreeNode(const pair<K,V>& kv):_kv(kv),_parent(nullptr),_left(nullptr),_right(nullptr),_col(RED){}
};template<class K,class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
private:Node* _root = nullptr;public:bool insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;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 < cur->_kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfater = parent->_parent;assert(grandfater);//祖父颜色不为黑 说明红黑树在插入之前就是不平衡的assert(grandfater->_col == BLACK);if (parent == grandfater->_left){Node* uncle = grandfater->_right;if (uncle && uncle->_col == RED){grandfater->_col = RED;parent->_col = uncle->_col = BLACK;cur = grandfater;parent = cur->_parent;}else{if (parent->_left == cur){//    g//  p   u//cRotateR(grandfater);parent->_col = BLACK;grandfater->_col = RED;}else{//    g//  p   u//    cRotateL(parent);RotateR(grandfater);cur->_col = BLACK;grandfater->_col = RED;}break;}}else{Node* uncle = grandfater->_left;if (uncle && uncle->_col == RED){grandfater->_col = RED;parent->_col = uncle->_col = BLACK;cur = grandfater;parent = cur->_parent;}else{if (parent->_right == cur){//    g//  u   p//        cRotateL(grandfater);parent->_col = BLACK;grandfater->_col = RED;}else{//    g//  u   p//     cRotateR(parent);RotateL(grandfater);cur->_col = BLACK;grandfater->_col = RED;}break;}}}_root->_col = BLACK;return true;}void Inorder(){_Inorder(_root);}bool IsBalance(){if (_root == nullptr){return false;}if (_root->_col == RED){cout << "根节点为红色,不是红黑树" << endl;return false;}int benchmark = 0;return PrevCheck(_root, 0, benchmark);}private:bool PrevCheck(Node* root, int blackNum, int& benchmark){if (root == nullptr){if (benchmark == 0){blackNum = benchmark;return true;//第一次遍历到空 没有比较意义 将第一次的黑色节点作为参考去判断}if (blackNum != benchmark){cout << "红黑树各路黑色节点数量不相同" << endl;return false;}else{return true;}}if (root->_col == BLACK){blackNum++;}if (root->_col == RED && root->_parent->_col == RED){cout << "出现连续红色节点,不是红黑树" << endl;return false;}return PrevCheck(root->_left,blackNum,benchmark) && PrevCheck(root->_right,blackNum,benchmark);}void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_Inorder(root->_right);}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR){subLR->_parent = parent;}Node* ppNode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (ppNode == nullptr){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;subL->_parent = ppNode;}else{ppNode->_right = subL;subL->_parent = ppNode;}}}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL){subRL->_parent = parent;}Node* ppNode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (ppNode == nullptr){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}}
};
void TestRBTree1()
{int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 0,5,30,25,20,4,13,30,28,27 };  // 测试双旋平衡因子调节//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };RBTree<int, int> t1;for (auto e : a){t1.insert(make_pair(e, e));}t1.Inorder();cout << "IsBalance:" << t1.IsBalance() << endl;
}void TestRBTree2()
{size_t N = 1000;srand(time(0));RBTree<int, int> t1;for (size_t i = 0; i < N; ++i){int x = rand();cout << "Insert:" << x << ":" << i << endl;t1.insert(make_pair(x, i));}cout << "IsBalance:" << t1.IsBalance() << endl;
}

这篇关于红黑树——原理刨析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/356985

相关文章

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

深度解析Spring AOP @Aspect 原理、实战与最佳实践教程

《深度解析SpringAOP@Aspect原理、实战与最佳实践教程》文章系统讲解了SpringAOP核心概念、实现方式及原理,涵盖横切关注点分离、代理机制(JDK/CGLIB)、切入点类型、性能... 目录1. @ASPect 核心概念1.1 AOP 编程范式1.2 @Aspect 关键特性2. 完整代码实

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重

Spring @Scheduled注解及工作原理

《Spring@Scheduled注解及工作原理》Spring的@Scheduled注解用于标记定时任务,无需额外库,需配置@EnableScheduling,设置fixedRate、fixedDe... 目录1.@Scheduled注解定义2.配置 @Scheduled2.1 开启定时任务支持2.2 创建

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

Python中使用uv创建环境及原理举例详解

《Python中使用uv创建环境及原理举例详解》uv是Astral团队开发的高性能Python工具,整合包管理、虚拟环境、Python版本控制等功能,:本文主要介绍Python中使用uv创建环境及... 目录一、uv工具简介核心特点:二、安装uv1. 通过pip安装2. 通过脚本安装验证安装:配置镜像源(可

Mysql的主从同步/复制的原理分析

《Mysql的主从同步/复制的原理分析》:本文主要介绍Mysql的主从同步/复制的原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录为什么要主从同步?mysql主从同步架构有哪些?Mysql主从复制的原理/整体流程级联复制架构为什么好?Mysql主从复制注意

Nacos注册中心和配置中心的底层原理全面解读

《Nacos注册中心和配置中心的底层原理全面解读》:本文主要介绍Nacos注册中心和配置中心的底层原理的全面解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录临时实例和永久实例为什么 Nacos 要将服务实例分为临时实例和永久实例?1.x 版本和2.x版本的区别

apache的commons-pool2原理与使用实践记录

《apache的commons-pool2原理与使用实践记录》ApacheCommonsPool2是一个高效的对象池化框架,通过复用昂贵资源(如数据库连接、线程、网络连接)优化系统性能,这篇文章主... 目录一、核心原理与组件二、使用步骤详解(以数据库连接池为例)三、高级配置与优化四、典型应用场景五、注意事