C++之红黑树

红黑树

  • 红黑树的概念
  • 红黑树的性质
  • 红黑树结点的定义
  • 红黑树的插入
  • 红黑树的验证
  • 红黑树与AVL树的比较

红黑树的概念

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

红黑树的性质

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

为什么红黑树就能保证其最长路径中节点个数不会超过最短路径节点个数的两倍?

从性质3,4可以得出,一棵红黑树的最短可能路径就是全为黑结点,即为N:
在这里插入图片描述
而最长可能路径就是由一黑一红结点构成的路径,该路径当中黑色结点与红色结点的数目相同,即长度为2N ;
在这里插入图片描述

红黑树结点的定义

红黑树结点的定义其实和AVL树差不多,只不过红黑树结点少了平衡因子,多了颜色。

//枚举结点颜色
enum color
{RED,BLACK
};
template <class K, class V>
struct RBTreeNode
{//三叉链结构RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;//存储的键值对pair<K, V> _kv;//结点颜色color _col;//构造函数RBTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED){}
};

为什么构造结点时,默认将结点的颜色设置为红色?

我们可以发现,红黑树的有一条性质是所有路径黑色结点的数目都相等,如果欧姆尼新插入的结点默认为黑色,就会破坏这条性质,我们就需要对红黑树进行调整。

如果我们插入的是红结点,此时如果父结点是红结点,我们就需要进行调整,如果父结点是黑结点,我们就不需要进行调整。

红黑树的插入

红黑树的插入步骤分为以下三步:

  1. 根据二叉搜索树的性质,找到待插入位置;
  2. 在待插入位置插入新结点;
  3. 如果父结点为红色,就进行调整。

我们可以发现,前两步其实AVL树没有区别,我们只要在第三步进行调整就可以了。

那么,怎么对红黑树插入结点进行调整呢?

如果我们插入结点的父结点是黑色的,我们就不需要进行任何调整,因为这并没有破坏红黑树的性质;如果我们插入结点的父结点是红结点,我们就需要对红黑树进行调整了。

插入结点的父结点为红色,就说明祖父结点一定存在且为黑色,对红黑树的调整主要是对叔叔结点的调整。

红黑树的调整一共分为三种方式:

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

在这里插入图片描述
插入结点为红,父结点为红,我们需要将父结点与叔叔结点都调整为黑色,然后将祖父结点调整为红色,如果此时g这棵树不为子树,就再将祖父结点调整为黑色;

对应的抽象图如下:
在这里插入图片描述
如果g不为子树,最后将g调整为黑色就完成了红黑树的调整;
在这里插入图片描述
如果g为子树,一次调整以后g还有双亲,双亲如果是红色,将p,u改为黑,g改为红,然后把g当成cur,继续向上调整,还要继续向上调整。

2.cur为红,p为红,g为黑,u不存在/u存在且为黑,p为g的左孩子,cur为p的左孩子或者p为g的右孩子,cur为g的右孩子

我们均已被插入结点在根结点左侧为例:

如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点则cur和p一定有一个节点的颜色是黑色,就不满足性质:每条路径黑色节点个数相同。
在这里插入图片描述
此时我们只需要以p为旋转点进行右单旋,然后进行颜色调整即可:
在这里插入图片描述
当u存在且为黑时:

此时一定是情况一向上调整才会出现的情况,这种情况下cur不会是新插入的结点,是经过前一次调整以后的结点:
在这里插入图片描述
我们以新插入结点在a的左侧为例:
在这里插入图片描述

此时我们以g为旋转点进行右单旋,然后进行颜色调整,红黑树即可平衡;

同样,如果被插入结点在根结点右侧,我们只需要进行相应左旋在调整颜色即可实现红黑树平衡;

抽象图如下:

被插入结点在根结点左侧:
在这里插入图片描述
被插入结点在根结点右侧:
在这里插入图片描述

3.cur为红,p为红,g为黑,u不存在/u存在且为黑,p为g的左孩子,cur为p的右孩子或者p为g的右孩子,cur为p的左孩子

u不存在时:
在这里插入图片描述

当u存在且为黑时,我们以下面这种情况为例:
在这里插入图片描述
我们先已p为轴点进行左单旋,再以g为轴点进行右单旋,最后进行颜色调整,红黑树保持平衡;

抽象图如下:

被插入结点在根结点左侧:
在这里插入图片描述
被插入结点在根结点右侧:
在这里插入图片描述
代码实现:

bool Insert(const pair<K, V>& kv)
{//根结点为空if (_root == nullptr){//创建根结点_root = new Node(kv);//颜色变为黑_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){//如果插入key值小于根结点key值if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}//如果插入key值大于根结点key值else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}//相等,返回falseelse{return false;}}//创建cur结点cur = new Node(kv);//颜色初始化为红色cur->_col = RED;//如果插入key值小于parent结点key值if (parent->_kv.first > kv.first){//在左边插入parent->_left = cur;}//如果插入key值大于parent结点key值else{//在右边插入parent->_right = cur;}//跟父结点连接起来cur->_parent = parent;while (parent && parent->_col == RED){//定义祖父节点Node* grandfather = parent->_parent;assert(grandfather);assert(grandfather->_col == BLACK);//如果父结点在祖父节点左边if (parent == grandfather->_left){//定义叔叔结点在祖父结点右边Node* uncle = grandfather->_right;//情况一://叔叔结点存在且为红if (uncle && uncle->_col == RED){//进行颜色调整parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续向上调整cur = grandfather;parent = cur->_parent;}//情况二和三://叔叔结点不存在或者存在且为黑else{//插入结点在父结点左边if (cur == parent->_left){//右单旋+颜色调整RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}//插入结点在父结点左边else{//左右双旋+颜色调整RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}//如果父结点在祖父节点右边else{//定义叔叔结点在祖父结点左边Node* uncle = grandfather->_left;//情况一://叔叔结点存在且为红if (uncle && uncle->_col == RED){//颜色调整parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//向上调整cur = grandfather;parent = cur->_parent;}//情况二和三://叔叔结点不存在或者存在且为黑else{//插入结点在父结点右边if (cur == parent->_right){//左单旋+颜色调整RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}//插入结点在父结点左边else{//右左双旋+颜色调整RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}//根结点变为黑色_root->_col = BLACK;return true;
}

红黑树的验证

我们可以判断每条路径的黑节点数量是否相等来判断该二叉树是否为红黑树:

void InOrder()
{_InOrder(_root);cout << endl;
}bool IsBalance()
{//如果根结点为空,返回true;if (_root == nullptr){return true;}//如果根结点为红色,则返回false;if (_root->_col == RED){cout << "根结点不是黑色" << endl;return false;}//定义一个黑色结点数量的基准值,为最左边路径黑色结点数量int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){benchmark++;}cur = cur->_left;}//黑色结点数量int BlackNum = 0;return PrevCheck(_root, BlackNum, benchmark);
}
bool PrevCheck(Node* root, int BlackNum, int benchmark)
{//说明此时路径已经走完了if (root == nullptr){//如果基准值不等于黑色结点数量,返回falseif (benchmark != BlackNum){cout << " 某条黑色结点数量不相等" << endl;return false;}//否则返回truereturn true;}//根结点为黑色,黑色结点数量++if (root->_col == BLACK){BlackNum++;}//存在连续红色节点,返回falseif (root->_col == RED && root->_parent->_col == RED){cout << "存在连续的红色结点" << endl;return false;}//递归进行判断return PrevCheck(root->_left, BlackNum, benchmark)&& PrevCheck(root->_right, BlackNum, benchmark);
}

红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单。

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

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

相关文章

Spark 6:Spark SQL DataFrame

SparkSQL 是Spark的一个模块, 用于处理海量结构化数据。 SparkSQL是用于处理大规模结构化数据的计算引擎 SparkSQL在企业中广泛使用&#xff0c;并性能极好 SparkSQL&#xff1a;使用简单、API统一、兼容HIVE、支持标准化JDBC和ODBC连接 SparkSQL 2014年正式发布&#xff0c;当…

Support for password authentication was removed on August 13, 2021 解决方案

打开你的github&#xff0c;Setting 点击Developer settings。 点击generate new token 按照需要选择scope 生成token&#xff0c;以后复制下来。 给git设置token样式的remote url git remote set-url origin https://你的tokengithub.com/你的git用户名/仓库名称.git然后就可…

【List篇】使用Arrays.asList生成的List集合,操作add方法报错

早上到公司&#xff0c;刚到工位&#xff0c;测试同事就跑来说"功能不行了&#xff0c;报服务器异常了&#xff0c;咋回事";我一脸蒙&#xff0c;早饭都顾不上吃&#xff0c;要来了测试账号复现了一下&#xff0c;然后仔细观察测试服务器日志&#xff0c;发现报了一个…

Redis缓存预热、缓存雪崩、缓存击穿、缓存穿透

文章目录 Redis缓存预热、缓存雪崩、缓存击穿、缓存穿透一、缓存预热1、问题排查2、解决方案&#xff08;1&#xff09;准备工作&#xff08;2&#xff09;实施&#xff08;3&#xff09;总结 二、缓存雪崩1、解决方案 三、缓存击穿1、解决方案&#xff08;1&#xff09;互斥锁…

Mavan进阶之多模块(聚合)

文章目录 Maven 多模块&#xff08;聚合&#xff09;非父子关系的多模块项目 Maven 多模块&#xff08;聚合&#xff09; Maven 继承和聚合是 2 个独立的概念。工程与工程之间可能毫无关系&#xff0c;也可能是继承关系&#xff0c;也可能是聚合关系&#xff0c;也可能既是继承…

C#学习 - 初识类与名称空间

类&#xff08;class&#xff09;& 名称空间&#xff08;namespace&#xff09; 类是最基础的 C# 类型&#xff0c;是一个数据结构&#xff0c;是构成程序的主体 名称空间以树型结构组织类 using System; //前面的using就是引用名称空间 //相当于C语言的 #include <..…

maven聚合工程的创建

父工程&#xff1a; parent-project 子工程&#xff1a;project-child project-child2 project-child3 创建父工程 将src目录删除了(在父工程中的src目录是没有用的&#xff09; 创建子工程 右击父工程------new------module 聚合工程创建完之后 在父工程的pom文件中 …

【送书活动】全网超50万粉丝的Linux大咖良许,出书了!

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

复制tr的一行数据或者复制数据使用,使用jq和php

效果图&#xff1a; 2.Html <!--复制的tr数据&#xff0c;s----------------------------------------------------------------------------------------------->{foreach from$arrs keykk itemvv} <tr><td style"text-align:center;" >1</t…

USB接口发展历程大全

1996年&#xff0c;由英特尔、微软、ibm等多家公司联合设计的usb标准问世&#xff0c;键盘、鼠标、智能手机以及打印机等等大多使用usb标准来实现供电和数据传输。 usb接口从诞生之初就是为了实现通用这个目的。在usb诞生之前&#xff0c;键盘、鼠标多使用ps二接口&#xff0c…

Arrays.copyOf 和System.arraycopy?深拷贝和浅拷贝?

Arrays.copyOf 和 System.arraycopy 1&#xff09;二者有何不同&#xff1f; System.arraycopy()方法 System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length); 需主动创建目标对象dest可定义起始元素&#xff0c;灵活拷贝元素比较重要的一点&…

【python自动化】playwright长截图切换标签页JS注入实战

前言 当前教程使用的playwright版本为1.37.0,selenium版本为3.141.0 官方文档&#xff1a;https://playwright.dev/python/docs/screenshots 本教程目录如下 文章目录 前言playwright各类截图源码阅读ElementHandle类下的截图Page类下的截图Locator类下的截图 Playwright快速…