学习红黑树之前你应该保证你学过AVL树,也就是平衡二叉搜索树
数据结构 AVL树

AVL树是一棵高度平衡的二叉搜索树,其要求每个结点的高度差不能大于1,这样子就保证了其查询的时间复杂度为log2(N),不会出现单支树而导致时间复杂度退化到线性时间。但是AVL树的插入和删除性能非常低下,只要稍微不平衡,都需要进行旋转操作,实现起来也相对困难。所以如果当你需要查询效率高且有序时你可以有优先选择AVL树,但是如果你所需的数据结构是频繁的插入与删除,且需要高效查询且有序时,你可以优先考虑红黑树(RBTree)

红黑树与AVL树的比较:AVL树的查询效率虽然优于红黑树,但是这种差距不大,在cpu的快速调度下,这种差距可以忽略不计。而红黑树的插入与删除的性能要高于AVL树,AVL树插入一个节点或者删除一个节点都有可能需要旋转,且有可能追溯至根节点;红黑树是只有特定情况下才旋转,且很少会追溯至根节点。所以说红黑树是AVL树的一种更优的迭代版

红黑树

红黑树是一棵二叉搜索树,每个结点都增加了存储颜色的标志,这个颜色只有红色RED或者黑色BLACK。通过任意一条从跟结点到叶子结点的路径上颜色的限制,从而确保红黑树的最长路径不会超过最短路径的两倍,因此就可以达到近似平衡的效果

红黑树的性质

  1. 每个结点的颜色只有红色和黑色,且根节点必须为黑色
  2. 不能存在连续的红色结点,也就说如果一个结点为红色,那么它的左右孩子就必须为黑色
  3. 对于每个结点来说,从该结点到其所有后代结点的简单路径上,经过的黑色结点的数目应该是相同的

由这三条性质就能知道为什么红黑树可以保证最长路径不会超过最短路径的两倍了。如下图

最短路径的路径上的结点全为黑色结点,最长路径则是黑红相间,而要保证黑色结点数目相同且不能出现连续的两个红结点。从而确定了最长路径不会超过最短路径的两倍

红黑树的实现

结点信息

结点信息和AVL树类似,有5个属性,分别是父节点_parent,左孩子_left,右孩子_right,结点的值_val,颜色信息_color。其中节点都是该结点的指针,结点的值我们可以使用泛型T来实现,颜色我们可以通过bool值或者枚举来实现,这里为了提高可读性,我们使用枚举来实现。其构造函数参数就为pair,设置为缺省参数。

enum COLOR
{BLACK,RED
};template <class T>
struct RBTreeNode
{RBTreeNode<T>* _parent; //父节点RBTreeNode<T>* _left; //左孩子RBTreeNode<T>* _right; //右孩子T _val; //键值对COLOR _color; //颜色RBTreeNode(const T& val = T()):_parent(nullptr), _left(nullptr), _right(nullptr), _val(val), _color(RED){}
};

这里颜色为什么默认为红色呢?如果默认为黑色,那么就可以插入无论父亲是什么颜色,由于红黑树的性质,需要确保每条路径上的黑色结点个数相同,所以你此时插入了一个黑色结点,其他的路径上的黑色结点肯定会比插入新结点的这条路径少1个,调整起来就会非常繁琐。而当插入的结点为红色时,如果父节点的颜色为黑色,那么就直接插入;如果父节点为红色,那么就在该路径上进行颜色调整或者旋转操作。所以插入的结点默认为红色,调整的成本就远低于黑色


树的结构信息

只有一个成员根节点_root

template <class T>
class RBTree
{public:typedef RBTreeNode<T> Node;RBTree():_root(nullptr){}
private:Node* _root;
};

插入

红黑树的结点插入是红黑树的重点也是难点,插入结点和AVL的插入类似,只是最后颜色修改和调整会不一样

大致的步骤分为三步

  • 寻找到要插入的结点的位置
  • 创建新结点并插入新结点
  • 修改结点颜色或者调整树的结构

前两步实现简单,和AVL树类似,这里着重讲第三步颜色修改以及结构调整。当父节点为黑色时,此时没有存在连续的两个红色结点,所以就不需要颜色修改以及结构调整;而当父节点为红色时,就会出现连续的红色结点,此时就有必要修改结点颜色或者调整结构。是修改颜色还是调整结构又分为三种情况

结点约定:cur为当前节点、parent为父节点、gfather为祖父结点、uncle为叔结点

第一种情况:爸叔通红就变色----cur为红色,parent结点为红色,uncle结点存在且为红色
调整方法:需要对该路径上的子树进行调整,parent结点和uncle结点都修改为黑色,祖父结点修改为红色,此时以祖父结点为根的子树就不存在连续的两个红色结点。但是由于祖父结点颜色也被修改了,此次需要向上追溯,判断是否还存在连续的两个红色结点。令cur = gfather结点向上追溯调整

第二种情况:爸红叔黑就旋转,爸红没叔也要旋,哪边黑往哪边转----cur为红色,parent结点为红色,uncle结点不存在或者为黑色。parent在gfather的左边,cur在parent的左边;或者parent在gfather的右边,cur在parent的右边

parent在gfather的左边,cur在parent的左边:以gfather结点为轴进行右旋,parent结点修改为黑色,gfather结点修改为红色

uncle结点不存在

uncle结点存在且为黑色

parent在gfather的右边,cur在parent的右边:以gfather结点为轴进行左旋,parent结点修改为黑色,gfather结点修改为红色

uncle结点不存在

uncle结点存在且为黑

第三种情况:爸红叔黑就旋转,爸红没叔也要旋,哪边黑往哪边转----cur为红色,parent结点为红色,uncle结点不存在或者为黑色。parent在gfather的左边,cur在parent的右边;或者parent在gfather的右边,cur在parent的左边

parent在gfather的左边,cur在parent的右边:先以parent结点为轴进行左旋,交换cur和parent结点,此时变成第二种情况,再对gfather结点进行右单旋,并修改parent颜色为黑色gfather颜色为红色

uncle结点不存在

uncle结点存在且为黑

parent在gfather的右边,cur在parent的左边:先以parent结点为轴进行右旋,交换cur和parent结点,此时变成第二种情况,再对gfather结点进行左单旋,并修改parent颜色为黑色gfather颜色为红色

uncle结点不存在

uncle结点存在且为黑

总结成顺口溜根结点为黑,新结点为红,只能黑连黑,不能红连红;爸叔通红就变色,没叔叔黑就旋转,哪边黑往哪边转
代码

 bool insert(const T& val){if (_root == nullptr){_root = new Node(val);//根节点为黑色_root->_color = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;//1.寻找到要插入的结点的位置while (cur){parent = cur;if (cur->_val == val)//key值相同插入失败return false;else if (cur->_val > val)cur = cur->_left;elsecur = cur->_right;}//2.创建节点cur = new Node(val);if (parent->_val > cur->_val)parent->_left = cur;elseparent->_right = cur;cur->_parent = parent;//3.颜色的修改或者结构的调整while (cur != _root && cur->_parent->_color == RED)//不为根且存在连续红色,则需要调整{parent = cur->_parent;Node* gfather = parent->_parent;if (gfather->_left == parent){Node* uncle = gfather->_right;//情况1.uncle存在且为红if (uncle && uncle->_color == RED){parent->_color = uncle->_color = BLACK;gfather->_color = RED;//向上追溯cur = gfather;}else{if (parent->_right == cur)//情况3{RotateL(parent);swap(cur, parent);}//情况2.uncle不存在或者uncle为黑RotateR(gfather);parent->_color = BLACK;gfather->_color = RED;break;}}else{Node* uncle = gfather->_left;if (uncle && uncle->_color == RED){parent->_color = uncle->_color = BLACK;gfather->_color = RED;//向上追溯cur = gfather;}else{if (parent->_left == cur){RotateR(parent);swap(cur, parent);}RotateL(gfather);parent->_color = BLACK;gfather->_color = RED;break;}}}//根节点为黑色_root->_color = BLACK;return true;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{Node* gfather = parent->_parent;if (gfather->_left == parent)gfather->_left = subR;elsegfather->_right = subR;subR->_parent = gfather;}subR->_left = parent;parent->_parent = subR;}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{Node* gfather = parent->_parent;if (gfather->_left == parent)gfather->_left = subL;elsegfather->_right = subL;subL->_parent = gfather;}subL->_right = parent;parent->_parent = subL;}

红黑树的验证

验证一棵树是否为红黑树,必须要满足以下条件

  1. 该树是一棵二叉搜索树,也就是中序遍历出来的结果必须是有序的
  2. 根节点必须是黑色的
  3. 每一条路径上的黑色结点个数相同
  4. 不能存在连续红色的节点

我们先针对第一个条件写出中序遍历来验证是否有序输出

 void inoder(){_inoder(_root);cout << endl;}void _inoder(Node* root){if (root){_inoder(root->_left);cout << root->_val << " ";_inoder(root->_right);}}

测试给出100个随机数
满足第一个条件:中序遍历有序

剩下的三个条件都是红黑树的性质,我们可以都写在一个函数中,并递归的判断子树是否也满足红黑树的性质条件

在判断每条路径上的黑色结点是否相同时,我们可以选择一条路径上的黑色结点作为基准值,然后将该值作为该树的每条路径的黑色结点数,只要有一条路径与其值不同,则表示该树不是红黑树。代码简单,注释很明确

 bool isRBTree(){if (_root == nullptr) //空树也属于红黑树return true;if (_root->_color == RED) //不满足条件二:根节点必须为黑色return false;int bCount = 0; //某条路径上的黑色结点数Node* cur = _root;while (cur){if (cur->_color == BLACK)++bCount;cur = cur->_left;}int pathCount = 0;return _isRBTree(_root, bCount, pathCount);//遍历判断每条路径上的黑色结点个数是否相同}bool _isRBTree(Node* root, const int bCount, int pathCount){if (root == nullptr)//路径走完,判断黑色结点个数{if (pathCount == bCount)return true;elsereturn false;//不满足条件三: 每一条路径上的黑色结点个数相同}if (root->_color == BLACK)//遇到黑色结点就++++pathCount;Node* parent = root->_parent;if (parent && parent->_color == RED && root->_color == RED)return false; //不满足条件四:不能存在连续红色的节点return _isRBTree(root->_left, bCount, pathCount) //继续遍历左子树和右子树&& _isRBTree(root->_right, bCount, pathCount);}

运行结果:满足后三种条件,为红黑树

数据结构 红黑树(RBTree)的原理与实现相关推荐

  1. 红黑树(一)之 原理和算法详细介绍---转帖

    目录 1 红黑树的介绍 2 红黑树的应用 3 红黑树的时间复杂度和相关证明 4 红黑树的基本操作(一) 左旋和右旋 5 红黑树的基本操作(二) 添加 6 红黑树的基本操作(三) 删除 作者:Sky W ...

  2. 红黑树(一)之 原理和算法详细介绍

    出处:http://www.cnblogs.com/skywang12345/p/3245399.html 概要 目录 1 红黑树的介绍 2 红黑树的应用 3 红黑树的时间复杂度和相关证明 4 红黑树 ...

  3. 数据结构 - 红黑树

    数据结构 - 红黑树 - 面试常问知识点 数据结构是面试中必定考查的知识点,面试者需要掌握几种经典的数据结构:线性表(数组.链表).栈与队列.树(二叉树.二叉查找树.平衡二叉树.红黑树).图. 本文主 ...

  4. 数据结构-红黑树插入结点示例

    数据结构-红黑树插入结点示例 1.红黑树简介 2.在线可视化生成红黑树工具 3.红黑树插入结点性质和规则 3.1.红黑树插入结点性质 3.2.红黑树插入结点规则 4.红黑树插入结点示例 4.1.红黑树 ...

  5. 红黑树RBTree原理(超易懂)

    红黑树脑图 目录 文章目录 红黑树的定义 四点特征 为什么红黑树是近似平衡的 (1)首先,我们来看,如果我们将红色节点从红黑树中去掉,那单纯包含黑色节点的红黑树的高度是多少呢? (2)现在把红色节点加 ...

  6. 数据结构-红黑树原理分析

    前言 在阅读HashMap源码的时候发现,java1.8的HashMap的链表实现增加了红黑树,当链表长度超过指定阈值8的时候回进行树化. 为了提高增删查的效率. 而红黑树又比较复杂,所以专门写一篇关 ...

  7. 数据结构---红黑树的原理

    产生原因 有了AVL树为啥需要红黑树呢,我们知道AVL树可以保证查询的时间复杂度为O(long 2^N),但是我们知道AVL树的插入操作,结点之间调整非常复杂,导致AVL树的性能非常低下. 红黑树,是 ...

  8. 红黑树(RB-Tree)比AVL强在哪?

    前言 以前本科同学在找工作的时候,就被面试官问到过关于红黑树的问题.因为当时我的知识面不广,所以也不知道红黑树是个什么东西,也没放在心上.在看过了STL源码后才知道原来有很多底层实现都用的红黑树.简单 ...

  9. java数据结构红黑树上旋下旋_存储系统的基本数据结构之一: 跳表 (SkipList)

    在接下来的系列文章中,我们将介绍一系列应用于存储以及IO子系统的数据结构.这些数据结构相互关联又有着巨大的区别,希望我们能够不辱使命的将他们分门别类的介绍清楚.本文为第一节,介绍一个简单而又有用的数据 ...

最新文章

  1. 【基础积累】1x1卷积到底有哪些用处?
  2. 修改ubuntu的sources.list源
  3. python deepcopy函数_用Python解数独[6]:递归获得最终答案
  4. 在 SAP Kyma 上部署一个 Go MSSQL API Endpoint
  5. jvm(2)-java内存区域
  6. docker时区问题
  7. python编写代码_用 Python 编写干净、可测试、高质量的代码
  8. 4、requests-基础
  9. 【SDOI 2014】数表
  10. 查看占用指定端口的程序
  11. mllib调参 spark_《Spark 官方文档》机器学习库(MLlib)指南
  12. 云服务器上部署pytorch,flask部署pytorch-服务端
  13. 图形图像会议期刊文章查询
  14. delphixe10linux,减小Delphi XE 以上版 编译出来的程序体积
  15. 《算法》第四版官网库及数据文件
  16. Java主流框架技术及少量前端框架使用与总结
  17. 单片机MCU OTA升级技术
  18. U盘_PE启动+U存储+kali三合一启动盘制作教程
  19. android 清理系统垃圾,安卓手机清理系统垃圾方法汇总
  20. 求绝对值(调用函数)

热门文章

  1. 张鹏 html 笔记,传智 张鹏 html+css 课程 笔记2(吐血整理)
  2. gamma校正_Log与Gamma校正视频
  3. 双向链表示意图_java双向链表示意图
  4. 字符串正反连接java_字符串正反连接(Java实现,超简单)
  5. 面向对象的3个基本要素和5个基本设计原则(整理)
  6. java 求向量的均值,标准数组——向量
  7. 基于JAVA+SpringMVC+MYSQL的企业员工管理系统
  8. excel实用技巧——vlookup函数
  9. jQuery事件绑定函数:on()与bind()的差别
  10. C++对txt文本进行读写操作