26 红黑树

2024-06-18 23:36
文章标签 红黑树 26

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

目录

1.概念
2.性质
3.节点定义
4.结构
5.插入
6.验证
7.删除
8.红黑树和avl树比较
9.应用

概念

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

在这里插入图片描述

性质

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

第5条指的是空节点,方便数出二叉树的路径,上面的红黑树共有11条路径,每条路径都有2个黑色节点

最短路径:全黑
最长路径:一黑一红间隔
假设共有N个黑色节点,最短路径logN,最长路径2logN
假设每条路径都有N个黑色节点,每条路径的结点数量[N,2*N]之间

avl树是绝对平衡,红黑树是相对平横,最长路径不超过最短路径的2倍

节点定义

枚举一个颜色,红和黑,节点中加入颜色

enum color
{RED,BLACK
};template <class K, class V>
struct TreeNode
{struct TreeNode<K, V>* _parent;struct TreeNode<K, V>* _left;struct TreeNode<K, V>* _right;std::pair<K, V> _kv;color _col;TreeNode(std::pair<K, V> kv):_parent(nullptr), _left(nullptr), _right(nullptr), _kv(kv), _col(RED){}
};

为什么节点默认是红色?
因为每条路径的黑色节点数量都必须相等,如果插入结点默认为黑色,就会对所有路径造成影响。如果是红色,最多只会影响自己所在的路径

红黑树结构

为了后续实现关联式容器简单,红黑树的实现中可以增加一个头结点,因为根节点必须为黑色,为了与根节点区分,将头结点给成黑色,并且让头结点的pParent域指向红黑树的根节点,pLeft域指向红黑树中最小的结点,_pRight域指向红黑树最大的结点,如下,本节的实现不采用头结点
在这里插入图片描述

插入

红黑树是在二叉搜索树的基础上加入其平衡限制条件,因此红黑树的插入可分为两步:

插入结点

#pragma once
#include <iostream>
#include <assert.h>
#include <queue>enum color
{RED,BLACK
};template <class K, class V>
struct TreeNode
{struct TreeNode<K, V>* _parent;struct TreeNode<K, V>* _left;struct TreeNode<K, V>* _right;std::pair<K, V> _kv;color _col;TreeNode(std::pair<K, V> kv):_parent(nullptr), _left(nullptr), _right(nullptr), _kv(kv), _col(RED){}
};template <class K, class V>
class RBTree
{typedef TreeNode<K, V> node;
public:bool insert(const std::pair<K, V>& kv){if (_root == nullptr){_root = new node(kv);_root->_col = BLACK;return true;}node* parent = nullptr;node* cur = _root;while (cur){parent = cur;if (kv.first < cur->_kv.first){cur = cur->_left;}else if (kv.first > cur->_kv.first){cur = cur->_right;}else{return false;}}//插入cur = new node(kv);cur->_parent = parent;if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}	}private:node* _root = nullptr;
};

检测新节点插入后,红黑树的性质是否遭到破坏

因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲颜色是红色时,就违反了性质三不能有连在一起的红色节点,此时分情况讨论:

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

1.当a/b/c/d/e都是空,cur就是新增
在这里插入图片描述

2.c/d/e是每条路径1个黑色节点的红黑树,那么就是x/y/z/r中任意一种
在这里插入图片描述
cur更改,变为黑色节点,左右各连a和b节点,cur就是在a和b插入,就会失衡
在这里插入图片描述

cde共有444种可能,插入位置有4个,就是64*4=256种可能

3.当每条路径变为2个黑色节点的子树
在这里插入图片描述
在这里插入图片描述

第一个是C88,C87…C80,再加上下面两个,第一个2个红色节点是C42,可以换一边,就是C42*2,第二个是C44,如果这些是100种可能,c,d,e就有1003种可能,其他更不用算了

从上面所有肯能找出一种规律,解决插入的相关问题
如果插入结点的双亲是黑色,不需要处理,是红色时才需要下面内容

  • 情况一:cur为红,p为红,g为黑,u存在且为红
  • 注意:此处看到的树,可能是一颗完整的树,也可能是一颗子树
    在这里插入图片描述

上面的右边是左边变色而来,p和u变黑,g变红,就平衡了
如果g是根节点,调整完成后,需要改为黑色

如果g是子树的根,调整完成后,g的双亲如果是黑色,就要继续往上调整
在这里插入图片描述
解决方式,将p和u改为黑,g改为红,然后把g当做cur,继续向上调整

  • 情况二,cur为红,p为红,g为黑,u不存在/存在且为黑

在这里插入图片描述
u的情况有两种:
1.如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4
2.如果u节点存在,则一定是黑色的,那么cur原来的颜色也是黑色,现在看到的红色是因为cur的子树在调整的过程中将cur节点的颜色由黑色变为红色
在这里插入图片描述

在这里插入图片描述
p为g的左孩子,cur为p的左孩子,则进行右单旋转,相反
p为g的右孩子,cur为p的右孩子,则进行左单旋转

  • 情况三,cur为红,p为红,g为黑,u不存在/存在且为黑

和情况二类似
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转,旋转后变成了情况二,再右单旋转
p为g的右孩子,cur为p的左孩子,则针对p做右单旋转,旋转后变成了情况二,再左单旋转

在这里插入图片描述

调整

//父节点是红色调整
while (parent && parent->_col == RED)
{node* gdparent = parent->_parent;node* uncle;if (gdparent->_left == parent){uncle = gdparent->_right;//第一种情况 叔叔节点是红色,变色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;gdparent->_col = RED;}//第二种情况 分左右//     g//  par  un// curelse{if (cur == parent->_left){RotateRight(gdparent);parent->_col = BLACK;gdparent->_col = RED;}//     g//  par  un//    curelse{RotateLeft(parent);RotateRight(gdparent);cur->_col = BLACK;parent->_col = RED;gdparent->_col = RED;}break;}}else{uncle = gdparent->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;gdparent->_col = RED;}else{if (cur == parent->_right){RotateLeft(gdparent);parent->_col = BLACK;gdparent->_col = RED;}//     g//  par  un//    curelse{RotateRight(parent);RotateLeft(gdparent);cur->_col = BLACK;//parent->_col = RED;gdparent->_col = RED;}break;}}cur = gdparent;parent = cur->_parent;
}//根必须是黑色
_root->_col = BLACK;
return true;

根节点在最后必须变为黑色,在函数最后部分统一改变
首先判断par在g的左边的三种情况,叔叔节点存在且是红色直接变色,更新节点向上。叔叔节点不存在或为黑色,判断cur在par的哪边,采取不同的旋转,然后改变节点颜色
par在g右边是相反

插入过程:
在这里插入图片描述

红黑树的验证

红黑树的验证分为两步:
1.检测是否满足二叉搜索树的(中序遍历是否有序序列)
2.检测是否满足红黑树的性质
先判断根节点是否为黑色,再记录一条路径黑色节点数量用来参考,递归红黑树,遇见黑色节点,黑色节点数量++。如果是红色节点,判断它的父节点是否为红色。遇到空,判断这条路径黑色节点数量和参考值是否一样

bool IsBalance()
{if (_root->_col == RED){std::cout << "根节点是红色" << std::endl;return false;}int refVal = 0;  //记录最左路径黑色节点的数量,用来参考node* cur = _root;while (cur){if (cur->_col == BLACK){refVal++;}cur = cur->_left;}return _IsBalance(_root, 0, refVal);
}bool _IsBalance(node* cur, int blackNum, int refVal)
{if (cur == nullptr){//判断每条路径黑色节点数量正常if (blackNum != refVal){std::cout << "黑色节点数量不相等" << std::endl;return false;}return true;}if (cur->_col == BLACK){blackNum++;}if (cur->_col == RED && cur->_parent->_col == RED){std::cout << cur->_kv.first << " 连续红色节点" << std::endl;}return _IsBalance(cur->_left, blackNum, refVal) && _IsBalance(cur->_right, blackNum, refVal);
}

删除

参考《算法导论》或者《STL源码剖析》
https://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html

这里只提供思路
删除前部分内容和avl树都一样,如果删除的节点有两个孩子,就找前驱节点替换,这样所有情况都变成了删除节点只有一个孩子的情况,如果没有孩子也可以当做只有一个孩子处理

父亲节点称为par,删除节点del,孩子节点为child

  • 第一种情况,删除的结点是红色,它如果有孩子一定是黑色,只需要将par链接到child
  • 第二种情况,删除节点是黑色,child为红色,还是将par连接到child,将child改为黑色
  • 第三种情况,删除节点是黑色,child也为黑色,这时考察del的兄弟节点,v有两种情况

情况1,v是黑色节点,v的左子女是w,根据w的颜色讨论:
下图g为祖父节点,u为del的child节点,v是del的兄弟节点,del节点在g的右孩子,删除后g连接到u
当删除了del时,g的右边ul和ur少了一个黑色节点,g的左边的每条路径wl,wr,vr都有相等的黑色节点

根据节点w的状态,有以下情况:
设平衡路径有2个黑色节点
1.w是红色节点,设wl,wr,vr都有2个黑色节点,wl,wr,vr中各有一个黑色节点,ul和ur无黑色节点。以g为中心右旋,vr接到g的左边,v变为红色,w和g改为黑色。此时,wl和wr里面各有1个黑色,w也是黑色,还是2个。vr中有1个黑色,加上g也是2个,u是黑色,g是黑色,ul和ur路径也是两个黑色节点

2.w是黑色节点,这时看w的有兄弟r节点,根据r分情况
① r是红色节点,通过一次先左后右的双旋,将g染成黑色
wl和wr无黑色节点,rl和rr各有1个黑色。旋转完后,右边增加一个黑色节点
② r是黑色节点,r是黑色节点,这时就要看g的颜色,如果g是红色,只要交换g和子女v的颜色。如果g是黑色,可以做一次右单旋转,将节点v上升染成双重黑色,消除节点u的双重黑色,双重黑色向根的方向移动
在这里插入图片描述
情况2,v是红色节点,考察有子女r,r一定是黑色节点,在看r的左子女s,根绝s的颜色分两种情况:
(1)s是红色,先左后右双旋,s染黑色,r上升,使包含u的路径的黑高度加1
在这里插入图片描述

(2) s是黑色,再看s的右兄弟节点t,根绝t的颜色分情况:
① 如果t为红色,先以t左旋,t替补r的位置,然后再以t先左后右的双旋,消除u的双重黑色
在这里插入图片描述

② 若节点t是黑色,以v为轴右单旋转,并改变v和r的颜色
在这里插入图片描述

节点u是g的左子女的情况是镜像的

红黑树和avl树的比较

都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2N log2N),红黑树不追求绝对平衡,只要保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以再经常增删的结构中更优,实际中红黑树用的更多

红黑树的应用

1.C++STL库,map/set、mutil_map/mutil_set
2.java库
3.linux内核
4.其他一些库

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



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

相关文章

Linux下MySQL8.0.26安装教程

《Linux下MySQL8.0.26安装教程》文章详细介绍了如何在Linux系统上安装和配置MySQL,包括下载、解压、安装依赖、启动服务、获取默认密码、设置密码、支持远程登录以及创建表,感兴趣的朋友... 目录1.找到官网下载位置1.访问mysql存档2.下载社区版3.百度网盘中2.linux安装配置1.

红黑树的旋转

红黑树的基本性质 红黑树与普通的二叉搜索树不同,它在每个节点上附加了一个额外的属性——颜色,该颜色可以是红色或黑色。通过引入这些颜色,红黑树能够维持以下 5 个基本性质,以确保树的平衡性: 每个节点是红色或黑色。根节点是黑色。所有叶子节点(NIL 节点)是黑色。如果一个节点是红色的,那么它的两个子节点都是黑色的(即,红色节点不能有红色子节点)。从任一节点到其每个叶子节点的所有路径上都包含相同数

每日一题~cf 970 div3 (A思维,B小模拟,C二分,D排列数建图成环,E 26个字母暴力+前缀和,F 逆元,G 数论gcd )

A 题意: 有 a 个1 ,b 个2.问是否能将这些数划分为两个数值相等的集合。 输出 YES 或者 NO —————— 问题等价于 将数组 分成两个数值相同的数组。所以sum 应该是偶数。也就是说 1 的个数是偶数。在i1的个数是偶数的情况下,将 2 分成两份,如果2 的个数是偶数,OK。如果是奇数那么需要1来补齐,如果1 的个数大于等于2那么可以补齐。(1 的个数是偶数,需要2个1来补齐,剩下

C++笔记19•数据结构:红黑树(RBTree)•

红黑树 1.简介:         红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍,因而是接近平衡的。 当搜索二叉树退化为单支树时,搜索效率极低,为了使搜索效率高,建立平衡搜索二叉树就需要"平衡树"来解决。上一篇博客介绍了AVL树,这

26 页高清大数据开发代码速查表,提升效率必备!【可下载】

各大互联网公司高价抢夺数据人才,为谋求长期发展、获得高薪,很多人转行到了大数据领域。这条路人才虽缺,但要成为优秀大数据工程师并不轻松:别的不说,光学习新技术,巩固旧知识,就需要耗费大量时间精力,实属不易。 为帮助大家提高学习效率,方便日后查找和使用,这里整理了一份大数据开发代码速查表资料,内容包括 Spark、Hadoop 及 Hive 等大数据开发主要知识点。 由于篇幅原因,下面只展示了速查表

26 页高清分布式集群代码速查表,提升效率必备!【可下载】

各大互联网公司高价抢夺海量数据处理、分布式系统开发人才,为谋求长期发展、获得高薪,很多人转行到了大数据、分布式、集群运维领域。这条路人才虽缺,但并不轻松:别的不说,光学习新技术,巩固旧知识,就需要耗费大量时间精力,实属不易。 为帮助大家提高学习和工作效率,方便日后查找和使用其中涉及的知识点,这里整理了一份分布式/集群开发、运维的代码速查表资料,内容包括 Spark、Hadoop 及 Hive 等

(176)时序收敛--->(26)时序收敛二六

1 目录 (a)FPGA简介 (b)Verilog简介 (c)时钟简介 (d)时序收敛二六 (e)结束 1 FPGA简介 (a)FPGA(Field Programmable Gate Array)是在PAL (可编程阵列逻辑)、GAL(通用阵列逻辑)等可编程器件的基础上进一步发展的产物。它是作为专用集成电路(ASIC)领域中的一种半定制电路而出现的,既解决了定制电路的不足,又克服了

『功能项目』DOTween动态文字【26】

打开上一篇25协程生成怪物模型的项目, 本章要做的事情是用DOTween插件做一个动态文字效果 首先在资源商店中免费下载一个DOTween插件 新建脚本:DowteenFlicker.cs 编写脚本: using DG.Tweening;using UnityEngine;using UnityEngine.UI;public class DowteenFli

振动分析-26-频域分析之深入理解功率谱和功率谱密度的计算过程

1 什么是PSD(功率谱密度) 功率谱密度(Power Spectral Density),以及其与Autopower(自功率谱)的区别。 1.1 PSD的定义 PSD——Power Spectral Density是表征信号的功率能量与频率的关系的物理量。 PSD经常用来研究随机振动信号。 PSD通常根据频率分辨率做归一化。 对于振动数据,PSD的单位通常是g^2/Hz。这个单位看起来不

基于Python的机器学习系列(26):PyTorch中的梯度计算

在本篇中,我们将探讨PyTorch的autograd功能,它为张量操作提供自动微分。我们将学习如何使用torch.autograd工具计算梯度并进行反向传播。 自动微分(Autograd)         PyTorch的autograd包自动计算张量的梯度。当一个张量的.requires_grad属性被设置为True时,PyTorch会追踪该张量的所有操作。在计算完成后,您可