红黑树(有图解)

目录

介绍

概念

性质

模拟实现

结点定义 

插入 

保证平衡的原因

一般情况

特殊情况(uncle为黑) 

uncle不存在

旋转方式

右旋

迭代器

++ 

-- 

代码


介绍

概念

红黑树是一种自平衡的二叉搜索树

  • 它是在每个节点上引入额外的颜色信息,通过对任何一条从根到叶子的路径上各个结点着色方式的限制,确保没有一条路径会比其他路径长出俩倍,从而达到高度差的平衡
  • 保证了在最坏情况下的时间复杂度为O(log n)
  • 同时,它也是c++标准库中两种关联容器(set和map)的底层实现

性质

  • 结点颜色只有红色,黑色两种
  • 根结点必须是黑色
  • 每个叶子结点(也就是平常被我们忽略的空结点(NIL结点))都是黑色的
  • 不能有两个连续的红色节点(从上到下的路径来看)
  • 从任意一个节点到其每个叶子节点的路径必须包含相同数目的黑色节点,这被称为黑色高度(Black Height)
满足以上性质后,可以保证红黑树中,其最长路径中节点个数不会超过最短路径节点个数的两倍

模拟实现

结点定义 

  • 和avl树一样,需要频繁用到父结点,所以需要一个parent成员
  • 除此之外,他还需要存储每个结点的颜色信息
  • 库中定义:

  • stl库中,红黑树实际上还有一个哨兵位的头结点(当然没有也可以)
  • 可以看到,树中定义了一个成员变量header
  • 且可以从命名来看,header的left指向树的最小结点,right指向树的最大结点,parent指向树的根结点

其次,结点插入一般设置为红色

因为有不能有连续红色的性质,所以直接插入红色结点,有助于确保平衡

插入 

  • 首先,和avl树一样,需要先找到插入结点的位置(如果重复就不插入了)
  • 主要需要修改的是parent,uncle,grandfather的颜色,而cur(或是新插入结点)的位置确保是红色
  • !!!!注意!!!!一定要记住我们的红黑树是有一个头结点的
  • 记得一定要在旋转时  改根结点和头结点之间的连接
  • 以及判断循环的时候,有些结束条件就是遍历到头结点!!!

保证平衡的原因

  • 黑色结点数目相等:保证了每条路径的高度在可控范围内
  • 红色不能连续:保证红色节点的父节点和子节点之间的黑色节点数目是相等的,从而保持了黑色平衡性
  • 由于黑色结点数相等,虽然没有限制红色结点数,但不能有连续红色结点,这两个就保证红结点最多有黑结点个,最少没有,所以不会超过两倍

一般情况

需要让parent和uncle都变黑,而grandfather变红(注意:因为parent是红色,所以grandfather是一定存在的,注意性质嗷)

如果grandfather不是根结点,就需要继续向上调整,让cur指向grandfather的位置

如果是根结点,就结束

!!!!注意,循坏外一定要让根结点的颜色为黑,因为可能会调整根为红色

特殊情况(uncle为黑) 

一轮变色后,此时uncle为黑:

如果让parent,uncle变黑,grandfather变红,会让parent那条路径多一个黑色结点,而uncle那条路数目没有变,所以单纯的变色满足不了这种情况了

同理,uncle不存在也是一样 

uncle不存在

会发现,此时无法变色,否则会在parent分支中,多出一个黑色结点

而且,这个结构,是不是让我们想起了avl树中的右旋

所以,在这两种情况下,需要旋转+变色,才能保证红黑树的平衡

旋转方式

像上面这种情况,很明显是要对grandfather右旋,最后parent成为这支树的根结点

由此,旋转方式其实就是由cur,parent,grandfather的相对位置,来确定

右旋

像这个例子,就是非常典型的右旋

旋转后,让新的根结点为黑色,剩下两个为红色

这个变色方式确实很神奇

再来分析一下,上面的uncle为黑时的旋转方式

因为此时,这三个结点的相对位置依然是右旋

 

右旋后:

然后进行变色,依然是根为黑色,其他两个为红色:

很神奇吧,这样就能让红黑树平衡了 

其它旋转方式都是一样的,旋转完后让根为黑色,其他两个为红色即可 

迭代器

底层实际上就是结点指针,在此之上进行了封装

++ 

还记得我们如果要遍历平衡树得用中序遍历吗,指针的挪动也是借助中序的思想

中序:左根右

  • 那么如果当前结点有右树,就找到右树中的最小结点
  • 如果当前结点没有右子树,那么就判断它和父亲的位置
  • 如果父亲的左子树就是当前结点,那么++后就是父亲
  • 如果是当前结点在右树,说明parent这一支子树已经遍历完毕,该从parent的位置继续判断
  • 直到parent遍历到了phead的位置(那个头结点)

-- 

和上面的++类似,只不过是倒着走

也就是右根左的方向

  • 那么如果当前结点有左树,就找到左树中的最大结点
  • 如果当前结点没有左子树,那么就判断它和父亲的位置
  • 如果父亲的右子树就是当前结点,那么--后就是父亲
  • 如果是当前结点在左树,说明parent这一支子树已经遍历完毕,该从parent的位置继续判断
  • 直到parent遍历到了phead的位置(那个头结点)

代码

#pragma once#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <cassert>
#include <cstdlib>
#include <utility>// 有迭代器的红黑树
namespace my_RB_Tree
{enum colour{black,red};template <class T>struct RBTreeNode // 结点{RBTreeNode(const T &data): _left(nullptr),_right(nullptr),_parent(nullptr),_col(red),_data(data){}RBTreeNode *_left;RBTreeNode *_right;RBTreeNode *_parent;colour _col;T _data;};template <class T, class Ptr, class Ref> // T是元素类型,ptr是指针类型,ref是引用类型(后两种会有const类型)struct RBTreeIterator                    // 迭代器{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ptr, Ref> Self;RBTreeIterator(Node *pNode): _pNode(pNode){}// 让迭代器具有类似指针的行为Ref &operator*(){return _pNode->_data;}Ptr *operator->(){return &(_pNode->_data);}// 让迭代器可以移动:前置/后置++Self &operator++(){Increament();return *this;}Self operator++(int){Self tmp(*this);Increament();return tmp;}// 让迭代器可以移动:前置/后置--Self &operator--(){DeIncreament();return *this;}Self operator--(int){Self tmp(*this);DeIncreament();return tmp;}// 让迭代器可以比较bool operator!=(const Self &s) const{return _pNode != s._pNode;}bool operator==(const Self &s) const{return _pNode == s._pNode;}private:void Increament();void DeIncreament();Node *_pNode;};// 为了后序封装map和set,本代码的红黑树会有一个作为哨兵位的头结点template <class K, class T, class KeyOfT> // K是关键字的类型,T是元素类型(区分这两个的原因:会用该红黑树封装成set和map,而map是key_value的)// keyofT是返回关键字类型的值(否则map无法返回)class RBTree                              // 红黑树{public:typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, T *, T> iterator;typedef RBTreeIterator<T, const T *, const T> const_iterator;public:RBTree(){_pHead = new Node(T());_pHead->_left = _pHead;_pHead->_parent = nullptr;_pHead->_right = _pHead;}// 在红黑树中插入值为data的节点,插入成功返回true,否则返回falsestd::pair<iterator, bool> Insert(const T &data);// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptrNode *Find(const K &data);// 获取红黑树最左侧节点Node *LeftMost();// 获取红黑树最右侧节点Node *RightMost();iterator begin(){return iterator(LeftMost());}iterator end(){return iterator(_pHead);}const_iterator begin() const{return const_iterator(LeftMost());}const_iterator end() const{return const_iterator(_pHead);}// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测bool IsValidRBTRee(){Node *root = _pHead->_parent;if (root->_col == red){return false;}int count = 0;find_blacknode(count, _pHead->_parent);return _IsValidRBTRee(_pHead->_parent, count, 0);}private:bool _IsValidRBTRee(Node *pRoot, size_t blackCount, size_t pathBlack);// 左单旋void RotateL(Node *pParent);// 右单旋void RotateR(Node *pParent);// 为了操作树简单起见:获取根节点Node *&GetRoot(){return _pHead->_parent;}void find_blacknode(int &count, Node *root){if (root == nullptr){return;}if (root->_col == black){++count;}find_blacknode(count, root->_left);find_blacknode(count, root->_right);}private:Node *_pHead = nullptr;};template <class K, class T, class KeyOfT>void RBTree<K, T, KeyOfT>::RotateL(Node *pParent){Node *cur = pParent->_right, *curleft = cur->_left;// 连接p和cur左树,因为该位置被p占据pParent->_right = curleft;if (curleft){curleft->_parent = pParent;}// 连接父结点if (pParent->_parent != _pHead){Node *ppnode = pParent->_parent;if (ppnode->_left == pParent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}else{_pHead->_parent = cur;cur->_parent = _pHead;}// 连接p和curpParent->_parent = cur;cur->_left = pParent;}template <class K, class T, class KeyOfT>void RBTree<K, T, KeyOfT>::RotateR(Node *pParent){Node *cur = pParent->_left, *curright = cur->_right;// 连接p和cur右树,因为该位置被p占据pParent->_left = curright;if (curright){curright->_parent = pParent;}// 连接父结点if (pParent->_parent != _pHead){Node *ppnode = pParent->_parent;if (ppnode->_left == pParent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}else{_pHead->_parent = cur;cur->_parent = _pHead;}// 连接p和curpParent->_parent = cur;cur->_right = pParent;}template <class K, class T, class KeyOfT>typename RBTree<K, T, KeyOfT>::Node *RBTree<K, T, KeyOfT>::LeftMost(){Node *cur = _pHead->_parent;while (cur->_left){cur = cur->_left;}return cur;}template <class K, class T, class KeyOfT>typename RBTree<K, T, KeyOfT>::Node *RBTree<K, T, KeyOfT>::RightMost(){Node *cur = _pHead->_parent;while (cur->_right){cur = cur->_right;}return cur;}template <class K, class T, class KeyOfT>typename RBTree<K, T, KeyOfT>::Node *RBTree<K, T, KeyOfT>::Find(const K &data) // 注意这里,{Node *cur = _pHead->_parent;KeyOfT kot;while (cur){if (data > kot(cur->_data)){cur = cur->_right;}else if (data < kot(cur->_data)){cur = cur->_left;}else{return cur;}}return nullptr;}template <class K, class T, class KeyOfT>std::pair<typename RBTree<K, T, KeyOfT>::iterator, bool> RBTree<K, T, KeyOfT>::Insert(const T &data) // 为了和map适配,要返回pair类型//(first是插入元素所在的迭代器,second是bool值,判断是否成功插入){KeyOfT kot;Node *newnode = nullptr;if (_pHead->_parent == nullptr){newnode = new Node(data);newnode->_col = black;_pHead->_parent = newnode;newnode->_parent = _pHead;return std::make_pair(iterator(newnode), true);}else{Node *cur = _pHead->_parent, *parent = cur;while (cur){if (kot(data) > kot(cur->_data)){parent = cur;cur = cur->_right;}else if (kot(data) < kot(cur->_data)){parent = cur;cur = cur->_left;}else{return std::make_pair((iterator)cur, false);}}newnode = new Node(data);cur = newnode;cur->_parent = parent;if (kot(parent->_data) > kot(cur->_data)){parent->_left = cur;}else{parent->_right = cur;}Node *grandfather = nullptr;while (parent && parent->_col == red){grandfather = parent->_parent; // 因为父结点是红色,所以肯定有爷爷结点(注意红黑树规则:根结点必须是黑色)if (grandfather->_left == parent) // 确定父亲位置{Node *uncle = grandfather->_right; // 也就能确定叔叔位置if (uncle && uncle->_col == red){parent->_col = uncle->_col = black;grandfather->_col = red;}else // 如果uncle不存在/为黑,就需要旋转+变色了{// 需要先判断旋转类型(也就是判断 -- parent和cur的相对位置)if (parent->_left == cur){// 一条偏右的直线,需要右旋RotateR(grandfather);// 旋转完后parent成为根结点// 更改完结点指向后,就可以改颜色了(都是根结点为黑,另外两个为红)parent->_col = black;cur->_col = grandfather->_col = red; // 和cur一层}else{// 拐角在左边,也就是先左旋,再右旋RotateL(parent);RotateR(grandfather);// cur成为根结点// 改颜色cur->_col = black;parent->_col = grandfather->_col = red;}break;}}else // parent在grandfather的右树{Node *uncle = grandfather->_left;if (uncle && uncle->_col == red){parent->_col = uncle->_col = black;grandfather->_col = red;}else // 如果uncle不存在/为黑,就需要旋转+变色了{// 需要先判断旋转类型(也就是判断 -- parent和cur的相对位置)if (parent->_right == cur){// 一条偏左的直线,需要左旋RotateL(grandfather);parent->_col = black;cur->_col = grandfather->_col = red; // 和cur一层}else{// 拐角在right,也就是先右旋,再左旋RotateR(parent);RotateL(grandfather);// 改颜色cur->_col = black;parent->_col = grandfather->_col = red;}break;}}cur = grandfather; // 注意,这里会改cur的指向,但返回值需要返回插入位置的迭代器,所以需要另外保存parent = cur->_parent;}(_pHead->_parent)->_col = black; // 根结点必须为黑(防止它在上面的循环中被修改)}return std::make_pair(iterator(newnode), true);}template <class K, class T, class KeyOfT>bool RBTree<K, T, KeyOfT>::_IsValidRBTRee(Node *cur, size_t blackCount, size_t pathBlack){if (cur == nullptr){// 到空结点后,就说明一条路径已经走通了,可以用得到的黑色结点数与基准数对比,不一样就说明红黑树错误if (pathBlack != blackCount){return false;}else{return true;}}if (cur->_parent){Node *ppnode = cur->_parent;if (cur->_col == red && ppnode->_col == red){return false;}}if (cur->_col == black){++pathBlack;}return _IsValidRBTRee(cur->_left, blackCount, pathBlack) && _IsValidRBTRee(cur->_right, blackCount, pathBlack);}template <class T, class Ptr, class Ref>void RBTreeIterator<T, Ptr, Ref>::Increament(){Node *cur = _pNode, *parent = _pNode->_parent;if (cur->_right){// 找到右子树的最小结点Node *curright = cur->_right;while (curright->_left){curright = curright->_left;}_pNode = curright;}else{while (parent->_parent != cur && parent->_right == cur) // 找到cur是parent的左结点的位置,这样parent的位置就是下一个位置{cur = parent;parent = parent->_parent;}_pNode = parent;}}template <class T, class Ptr, class Ref>void RBTreeIterator<T, Ptr, Ref>::DeIncreament(){Node *cur = _pNode, *parent = _pNode->_parent;if (cur->_left){// 找到左子树的最大结点Node *curleft = cur->_left;while (curleft->_right){curleft = curleft->_right;}_pNode = curleft;}else{while (parent->_parent != cur && parent->_left == cur) // 找到cur是parent的左结点的位置,这样parent的位置就是下一个位置{cur = parent;parent = parent->_parent; }_pNode = parent;}}
}

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

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

相关文章

基于Dijkstra、A*和动态规划的移动机器人路径规划(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑…

优先级队列的模拟实现

目录 1. 优先级队列的概念 1.1堆的概念 1.2堆的性质 1.3堆的存储方式 2. 堆的创建 2.1堆的创建代码解析 2.2建堆的时间复杂度 2.3堆的插入 2.4 堆的删除 2.5常见习题 1. 优先级队列的概念 队列是一种先进先出 (FIFO) 的数据结构 &#xff0c;但有些情况下&#xff0c; 操作的数…

Windows下载AOSP

关于repo repo只是谷歌做的&#xff0c;方便下载安卓源码的工具&#xff0c;本质上是对下载清单进行批量处理&#xff0c;然后使用git克隆。 在windows上下载源码只需要自己处理即可。 具体做法 首先使用git克隆安卓源码清单 git clone https://mirrors.tuna.tsinghua.edu.…

C# 画参数可调调幅波

参阅此&#xff0c; 使用VC输出调幅波的数值和波形_c如何显示下位机传输过来的频谱信号 csdn_bcbobo21cn的博客-CSDN博客 用winform做一下&#xff1b; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Dra…

【计算机网络】HTTP协议详解(举例解释,超级详细)

文章目录 一、HTTP协议简单介绍 1、1 什么是HTTP协议 1、2 再次理解“协议” 二、HTTP请求 2、1 HTTP的工作过程 2、1、1 demo代码 2、2 URL 介绍 2、2、1 urlencode 和 urldecode 2、3 HTTP 请求格式 三、HTTP响应 3、1 响应demo 3、2 HTTP 响应格式 四、HTTP 请求和响应中的…

【小尘送书-第六期】《巧用ChatGPT轻松玩转新媒体运营》AI赋能运营全流程,帮你弯道超车、轻松攀登运营之巅

大家好&#xff0c;我是小尘&#xff0c;欢迎你的关注&#xff01;大家可以一起交流学习&#xff01;欢迎大家在CSDN后台私信我&#xff01;一起讨论学习&#xff0c;讨论如何找到满意的工作&#xff01; &#x1f468;‍&#x1f4bb;博主主页&#xff1a;小尘要自信 &#x1…

游戏逆向中的 NoClip 手段和安全应对方式

文章目录 墙壁边界寻找碰撞 NoClip 是一种典型的黑客行为&#xff0c;允许你穿过墙壁&#xff0c;所以 NoClip 又可以认为是避免碰撞体积的行为 墙壁边界 游戏中设置了碰撞体作为墙壁边界&#xff0c;是 玩家对象 和墙壁发生了碰撞&#xff0c;而不是 相机 玩家对象有他的 X…

8、Nacos服务注册服务端源码分析(七)

本文收录于专栏 Nacos 中 。 文章目录 前言确定前端路由CatalogController.listDetail()ServiceManager总结 前言 前文我们分析了Nacos中客户端注册时数据分发的设计链路&#xff0c;本文根据Nacos前端页面请求&#xff0c;看下前端页面中的服务列表的数据源于哪里。 确定前端…

【数据结构】红黑树(C++实现)

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;数据结构 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【数据…

类加载机制

类加载运行全过程 当我们用java命令运行某个类的main函数启动程序时&#xff0c;首先需要通过类加载器把主类加载到 JVM。 public class Math {public static final int initData 666;public static User user new User();public int compute() { //一个方法对应一块栈帧内…

Docker部署Nginx-常用命令

1.拉取 docker pull nginx 2. 查看镜像 docker images 3.保存镜像 docker save -o nginx.tar nginx:latest 4.删除镜像 docker rmi nginx:latest 5. 加载镜像 docker load -i nginx.tar 6. 运行Nginx docker run -d --name nginx -p 80:80 nginx 7.停掉Nginx容器 docker stop n…

Blender 导出 fbx 到虚幻引擎中丢失材质!!!(使用Blender导出内嵌材质的fbx即可解决)

目录 0 引言1 Blender导出内嵌纹理的fbx模型 0 引言 我在Blender处理了一些fbx模型后再次导出到UE中就经常出现&#xff0c;材质空白的情况&#xff08;如下图所示&#xff09;&#xff0c;今天终于找到问题原因&#xff0c;记录下来&#xff0c;让大家避免踩坑。 其实原因很简…

弧度、圆弧上的点、圆的半径(r)、弧长(s)之间的关系

要计算弧度和圆弧上的点&#xff0c;需要知道以下几个要素&#xff1a; 圆的半径&#xff08;r&#xff09;&#xff1a;即圆的中心到圆周上任意一点的距离。 弧长&#xff08;s&#xff09;&#xff1a;从圆周上的一个点到另一个点所经过的弧长。 弧度&#xff08;θ&#x…

【chainlit】使用chainlit部署chatgpt

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

HCQ1-1300-D【高速输入】

因为我的PLC固件比较旧。所以有些限制。【比如&#xff1a;编译不报错&#xff0c;下载PLC程序就报故障】我的PLC的高速输入类型只能是【hsi_ref】 所以&#xff0c;程序添加的高速输入模块只能是【1.0.1.0】版本 如果固件版本低&#xff0c;看下固件能支持的类型。选错的话&am…

串口数据包收发

数据包 把属于同一批的数据进行打包和分割&#xff0c;方便接收方进行识别 HEX数据包 思路&#xff1a;一个数据规定四个字节&#xff0c;以0xFF为包头&#xff0c;0xFE为包尾&#xff0c;当检测到0xFF时&#xff0c;接下来四个数据就是数据&#xff0c;接收到0xFE时&#x…

FFMPEG 视频类过滤器学习整理

addroi 作用 在视频帧上标记一块感兴趣的区域。 帧数据被原封不动地传递&#xff0c;但元数据被附加到帧&#xff0c;指示可能影响后续编码行为的感兴趣区域。可以通过多次应用过滤器来标记多个区域。 参数 qoffset: 应用在此区域的量化偏移。 参数范围&#xff1a;-1 ~ …

【JVM】第五篇 垃圾收集器G1和ZGC详解

导航 一. G1垃圾收集算法详解1. 大对象Humongous说明2. G1收集器执行一次GC运行的过程步骤3. G1垃圾收集分类4. G1垃圾收集器参数设置5. G1垃圾收集器的优化建议6. 适合使用G1垃圾收集器的场景?二. ZGC垃圾收集器详解1. NUMA与UMA2. 颜色指针3. ZGC的运作过程4. ZGC垃圾收集器…

开发中的前端和后端

一、引言 前端和后端是Web开发中两个不同的领域。 前端开发主要负责实现用户界面的设计和功能&#xff0c;包括网页的布局、样式和交互效果。前端开发使用HTML、CSS和JavaScript等技术来构建用户在浏览器中直接与之交互的界面。前端开发人员需要关注网页的可视化效果和用户体验…

【软件测试】自动化测试selenium(一)

文章目录 一. 什么是自动化测试二. Selenium的介绍1. Selenium是什么2. Selenium的特点3. Selenium的工作原理4. SeleniumJava的环境搭建 一. 什么是自动化测试 自动化测试是指使用软件工具或脚本来执行测试任务的过程&#xff0c;以替代人工进行重复性、繁琐或耗时的测试活动…