文章目录

  • 红黑树
    • 红黑树的概念
    • 红黑树的性质
  • 红黑树与AVL树
  • 红黑树的实现
    • 红黑树的节点
    • 红黑树的插入
    • 红黑树的查找
    • 红黑树的验证
  • 完整代码

红黑树

红黑树的概念

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

红黑树的性质

  1. 每个节点不是红色就是黑色
  2. 根节点必须为黑节点
  3. 如果一个节点是红的,则他的两个孩子是黑的(不存在连续的红结点)
  4. 从某一节点出发到其所有的叶子节点,其中经过的黑色节点数量相等
  5. 每个叶子节点是黑色的(这里的叶子节点指的是NULL节点)

根据这几种性质,是如何保证每一个节点的高度差不超过二倍呢?我们可以直接设想一下极端情况
对于一个结点,到其叶子所经过的最短路径全为黑节点,最长路径为一黑一红交错的路径。
在完全遵守上面所有的性质的情况下,最大的路径差即为下图,也就是二倍。


红黑树与AVL树

在之前的博客中,我介绍了另外一种平衡二叉树,AVL树,他和红黑树又有什么不同呢?为什么在大部分库里,如STL、Linux内核等大部分都采用的是红黑树呢?

首先就来分析他们的增删查改的效率。
因为红黑树保证了最大的路径差不超过二倍,所以其在最坏情况下,增删查改的效率是O(2logN)
而AVL树则因为多次单旋双旋保证了高度平衡,其最大的高度差不超过2,所以他的效率一直都是保持在O(logN)左右。

从这里来看,AVL树的效率比红黑树要高上不少,几乎接近两倍,但是因为其单位都是logN,这里的两倍几乎就可以忽略了。例如要查找十亿个的数据(只考虑查找),最坏情况下,AVL树要30次,而红黑树要60次,虽然差距是两倍,但是这两倍对于计算机来说几乎已经可以忽略不计。并且,AVL树的高度平衡是因为其通过大量的旋转来完成的,所以对于经常发生删除和插入的结构,红黑树的效率会更优,并且红黑树的实现比起AVL更加容易且易于控制,所以实际中使用红黑树更多。

这是之前看到的一张图,可以从上图看到,如果是比较查找时,此时红黑树略低于AVL树,但如果是插入或者删除,则稍微高于AVL,原因就是因为AVL需要维护其高度平衡的特性,所以进行更多的旋转,导致效率降低。

红黑树的实现

关于二叉搜索树的思路,以及旋转的思路在前两篇中已经写过,这里就不多赘述
数据结构 : 二叉搜索树的原理以及实现(C++)
数据结构 : AVL树的原理以及实现(C++)

红黑树的节点

 enum Color{BLACK,RED,};template<class T>struct RBTreeNode{typedef RBTreeNode<T> Node;RBTreeNode(const T& data = T(), const Color& color = RED) : _left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _color(color){}Node* _left;Node* _right;Node* _parent;T _data;Color _color;};

红黑树的节点和二叉搜索树差不多,只是多了一个颜色,在这里我为了代码更加直观所以用的是枚举

红黑树的插入

插入分为两个步骤

  1. 按照二叉搜索树的特性找到插入的位置
  2. 按照红黑树的特性进行调节

第一步骤之前几个博客都讲过了,这里就不多说,直接讲第二步骤。

首先依据红黑树的特性,我们要决定新增节点的颜色。
如果新插入的结点为红色,则有可能打破了不能有连续的红结点这一规则,而如果插入的是黑色,则又破坏了每个路径的黑节点数量相同这一规则。如果都会破坏的话,就需要选择一个方便修复的,而不能有连续的红结点这一规则,只需要进行一下颜色的变换或者旋转,就可以解决这个问题,所以新增节点都为红色

接着来探讨插入时的各种状况。

情况1:插入节点的父节点为黑色

此时是最优情况,因为没有破坏任何规则,所以此时无需任何处理。

情况2:插入节点的父节点为红色,叔叔节点也为红色,祖父为黑色

此时的处理方法也很简单,因为出现了连续的红色,所以只需要进行颜色的修正即可解决。
这里将父亲和叔叔变黑,祖父变红即可解决。同时,因为祖父之上可能还有其他节点,还需要从祖父的位置继续向上调节

情况3:孩子为红,父亲为红,叔叔为黑或者不存在,祖父为黑。孩子与父亲呈直线状态

这里有两种情况

  1. 如果叔叔存在,则此时的孩子节点可能是下面的子树在进行变色处理时,将其从原本的黑色变为了红色。(否则不满足路径黑节点数量相同的性质)
  2. 如果叔叔不存在,则此时的孩子节点是刚插入进来的结点,因为不能有连续的红结点,所以孩子和父亲必须有一个是黑色,但是此时又不满足黑节点数量相同的性质。

解决的方法也不难,只需要旋转+变色处理即可
和AVL树一样,因为父亲和孩子呈直线状态,所以只需要单旋即可。
如果父亲是祖父的左孩子,孩子是父亲的左孩子,此时祖父进行右单旋
如果父亲是祖父的右孩子,孩子是父亲的右孩子,此时祖父进行左单旋。

此时再将祖父变红,父亲变黑,即可完成处理

情况4:孩子为红,父亲为红,叔叔为黑或者不存在,祖父为黑。孩子与父亲呈折线状态
这种情况与上面的类似,如果此时孩子与父亲处理折线状态,就需要双旋 + 变色处理

如果父亲是祖父的左孩子,孩子是父亲的右孩子,父亲此时进行左单旋
如果父亲是祖父的右孩子,孩子是父亲的左孩子,父亲此时进行右单旋。

上图进行左单旋

此时我们发现,这个状态和情况3有点像,所以只需要交换父亲和孩子,就可以转换为情况3

接着再按照情况3再进行一次单旋处理即可。

bool Insert(const std::pair<K, V>& data)
{//按照二叉搜索树的规则先找到位置//创建根节点if (_root == nullptr){_root = new Node(data, BLACK);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (data.first > cur->_data.first){parent = cur;cur = cur->_right;}else if (data.first < cur->_data.first){parent = cur;cur = cur->_left;}else{return false;}}//新插入节点为红色cur = new Node(data, RED);//保存插入的结点,因为后面会往上更新红黑树,所以cur可能会变化。Node* newNode = cur;//判断插入位置if (cur->_data.first > parent->_data.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//更新红黑树,如果父节点的颜色为黑,则说明满足条件,不需要处理,如果为红,则说明不满足,需要处理。while (parent && parent->_color == RED){Node* ppNode = parent->_parent;//如果父节点为祖父的左子树if (ppNode->_left == parent){//此时判断叔叔节点的状态,红黑树状态取决于叔叔Node* uncle = ppNode->_right;//第一种情况,如果叔叔节点存在且为红,则直接把父亲和叔叔变黑,祖父节点边红即可。然后再从祖父的位置继续往上调整if (uncle && uncle->_color == RED){//变色uncle->_color = parent->_color = BLACK;ppNode->_color = RED;//继续往上cur = ppNode;parent = cur->_parent;}/*叔叔节点为黑或者不存在,此时有两种情况情况二:cur为父节点的左子树,即直线状态。情况三:cur为父节点的右子树,即折线状态。情况二单旋一次后更改颜色即可完成对于情况三,如果将其进行一次旋转后再稍微处理,就可以转换成情况二*/else{//因为这里的双旋和AVL不一样,可以不用处理平衡因子,所以如果为折线则先旋转处理后,将其转换为直线处理即可。//第三种情况,折线状态,转换为直线状态处理if (parent->_right == cur){RotateL(parent);//单旋后再交换节点,即可变成直线状态。std::swap(parent, cur);}//处理第二种状态RotateR(ppNode);parent->_color = BLACK;ppNode->_color = RED;//处理完成break;}}//如果父亲为祖父的右子树else{//此时叔叔为左子树。Node* uncle = ppNode->_left;if (uncle && uncle->_color == RED){uncle->_color = parent->_color = BLACK;ppNode->_color = RED;cur = ppNode;parent = cur->_parent;}else{if (parent->_left == cur){RotateR(parent);std::swap(cur, parent);}RotateL(ppNode);ppNode->_color = RED;parent->_color = BLACK;break;}}}//为了防止不小心把根节点改为红色,最后手动改为黑色即可_root->_color = BLACK;return true;
}

旋转的思路已经讲过很多次了,这里就直接给代码,如果想了解具体思路可以点击上面AVL树那一篇博客

//右旋
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;//如果subLR存在,则让他的父节点指向parent。if (subLR){subLR->_parent = parent;}subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;//两种情况//如果parent为根节点,则让subL成为新的根节点if (parent == _root){_root = subL;subL->_parent = nullptr;}//如果不是根节点,则改变subL与其祖父节点的指向关系else{if (ppNode->_left == parent){ppNode->_left = subL;}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;}subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}
}

红黑树的查找

这里的查找和二叉搜索树一样,就不多说了

bool Find(const std::pair<K, V>& data)
{//根据二叉搜索树的性质,从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树Node* cur = _root;while (cur){//比根节点大则查找右子树if (data.first > cur->_data.first){cur = cur->_right;}//比根节点小则查找左子树else if (data.first < cur->_data.first){cur = cur->_left;}//相同则返回else{return true;}}//遍历完则说明查找不到,返回falsereturn false;
}

红黑树的验证

要验证是否为红黑树,只需要判断其是否满足这三个性质。
1. 根节点必须为黑节点
2. 不存在连续的红结点
3. 从某一节点出发到其所有的叶子节点,其中经过的黑色节点数量相等

bool IsRBTree()
{if (_root == nullptr){//空树也是红黑树return true;}//违反性质1if (_root->_color != BLACK){return false;}//获取从根节点出发的任意一条子路径的黑色节点数,这里选取最左子树。Node* cur = _root;size_t blackCount = 0;size_t count = 0;while (cur){if (cur->_color == BLACK){blackCount++;}cur = cur->_left;}//递归判断其他路径的黑色节点数return _IsRBTree(_root, count, blackCount);
}bool _IsRBTree(Node* root, size_t count, const size_t blackCount)
{//此时说明已经走到叶子节点,判断黑色节点数是否相等,不相等则违反性质3if (root == nullptr){if (count != blackCount){return false;}else{return true;}}//如果没走完,就接着判断其他情况//判断性质2,如果存在连续的红结点,则返回错误Node* parent = root->_parent;if (parent && root->_color == RED && parent->_color == RED){return false;}//如果当前节点为黑色,则记录if (root->_color == BLACK){count++;}//接着递归判断当前节点的所有路径return _IsRBTree(root->_left, count, blackCount) && _IsRBTree(root->_right, count, blackCount);
}

完整代码

#pragma once
#include<iostream>namespace lee
{enum Color{BLACK,RED,};template<class K, class V>struct RBTreeNode{typedef RBTreeNode<K, V> Node;RBTreeNode(const std::pair<K, V>& data = std::pair<K, V>(), const Color& color = RED): _left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _color(color){}Node* _left;Node* _right;Node* _parent;std::pair<K, V> _data;Color _color;};template<class K, class V>class RBTree{public:typedef RBTreeNode<K, V> Node;RBTree(): _root(nullptr){}~RBTree(){destory(_root);}void _InOrderTravel(Node* root) const{if (root == nullptr)return;_InOrderTravel(root->_left);std::cout << root->_data.first << ':' << root->_data.second << std::endl;_InOrderTravel(root->_right);}void InOrderTravel() const{_InOrderTravel(_root);}void destory(Node*& root){Node* node = root;if (!root)return;destory(node->_left);destory(node->_right);delete node;node = nullptr;}//右旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;//如果subLR存在,则让他的父节点指向parent。if (subLR){subLR->_parent = parent;}subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;//两种情况//如果parent为根节点,则让subL成为新的根节点if (parent == _root){_root = subL;subL->_parent = nullptr;}//如果不是根节点,则改变subL与其祖父节点的指向关系else{if (ppNode->_left == parent){ppNode->_left = subL;}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;}subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}}bool Find(const std::pair<K, V>& data){//根据二叉搜索树的性质,从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树Node* cur = _root;while (cur){//比根节点大则查找右子树if (data.first > cur->_data.first){cur = cur->_right;}//比根节点小则查找左子树else if (data.first < cur->_data.first){cur = cur->_left;}//相同则返回else{return true;}}//遍历完则说明查找不到,返回falsereturn false;}bool Insert(const std::pair<K, V>& data){//按照二叉搜索树的规则先找到位置//创建根节点if (_root == nullptr){_root = new Node(data, BLACK);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (data.first > cur->_data.first){parent = cur;cur = cur->_right;}else if (data.first < cur->_data.first){parent = cur;cur = cur->_left;}else{return false;}}//新插入节点为红色cur = new Node(data, RED);//保存插入的结点,因为后面会往上更新红黑树,所以cur可能会变化。Node* newNode = cur;//判断插入位置if (cur->_data.first > parent->_data.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//更新红黑树,如果父节点的颜色为黑,则说明满足条件,不需要处理,如果为红,则说明不满足,需要处理。while (parent && parent->_color == RED){Node* ppNode = parent->_parent;//如果父节点为祖父的左子树if (ppNode->_left == parent){//此时判断叔叔节点的状态,红黑树状态取决于叔叔Node* uncle = ppNode->_right;//第一种情况,如果叔叔节点存在且为红,则直接把父亲和叔叔变黑,祖父节点边红即可。然后再从祖父的位置继续往上调整if (uncle && uncle->_color == RED){//变色uncle->_color = parent->_color = BLACK;ppNode->_color = RED;//继续往上cur = ppNode;parent = cur->_parent;}/*叔叔节点为黑或者不存在,此时有两种情况情况二:cur为父节点的左子树,即直线状态。情况三:cur为父节点的右子树,即折线状态。情况二单旋一次后更改颜色即可完成对于情况三,如果将其进行一次旋转后再稍微处理,就可以转换成情况二*/else{//因为这里的双旋和AVL不一样,可以不用处理平衡因子,所以如果为折线则先旋转处理后,将其转换为直线处理即可。//第三种情况,折线状态,转换为直线状态处理if (parent->_right == cur){RotateL(parent);//单旋后再交换节点,即可变成直线状态。std::swap(parent, cur);}//处理第二种状态RotateR(ppNode);parent->_color = BLACK;ppNode->_color = RED;//处理完成break;}}//如果父亲为祖父的右子树else{//此时叔叔为左子树。Node* uncle = ppNode->_left;if (uncle && uncle->_color == RED){uncle->_color = parent->_color = BLACK;ppNode->_color = RED;cur = ppNode;parent = cur->_parent;}else{if (parent->_left == cur){RotateR(parent);std::swap(cur, parent);}RotateL(ppNode);ppNode->_color = RED;parent->_color = BLACK;break;}}}//为了防止不小心把根节点改为红色,最后手动改为黑色即可_root->_color = BLACK;return true;}/*判断是否为红黑树,就是判断其是否满足红黑树的特性特性:  1.根节点必须为黑节点2.不存在连续的红结点3.从某一节点出发到其所有的叶子节点,其中经过的黑色节点数量相等*/bool IsRBTree(){if (_root == nullptr){//空树也是红黑树return true;}//违反性质1if (_root->_color != BLACK){return false;}//获取从根节点出发的任意一条子路径的黑色节点数,这里选取最左子树。Node* cur = _root;size_t blackCount = 0;size_t count = 0;while (cur){if (cur->_color == BLACK){blackCount++;}cur = cur->_left;}//递归判断其他路径的黑色节点数return _IsRBTree(_root, count, blackCount);}bool _IsRBTree(Node* root, size_t count, const size_t blackCount){//此时说明已经走到叶子节点,判断黑色节点数是否相等,不相等则违反性质3if (root == nullptr){if (count != blackCount){return false;}else{return true;}}//如果没走完,就接着判断其他情况//判断性质2,如果存在连续的红结点,则返回错误Node* parent = root->_parent;if (parent && root->_color == RED && parent->_color == RED){return false;}//如果当前节点为黑色,则记录if (root->_color == BLACK){count++;}//接着递归判断当前节点的所有路径return _IsRBTree(root->_left, count, blackCount) && _IsRBTree(root->_right, count, blackCount);}private:Node* _root;};
}

高级数据结构与算法 | 红黑树(Red-Black Tree)相关推荐

  1. 红黑树Red/Black Tree

    红黑树Red/Black Tree 建立二进制搜索树,我们得到红/黑树,旨在解决BST可能变得不平衡的问题.(BST[二叉搜索树],是对于任意的node x,如果node y是node x的左边的节点 ...

  2. 数据结构--红黑树 Red Black Tree

    文章目录 1.概念 2.操作 2.1 左旋.右旋(围绕某个节点的左/右旋) 2.2 插入 2.3 删除 3. 代码 1.概念 二叉树在频繁动态增删后,可能退化成链表,时间复杂度由 O(lgn) 变成 ...

  3. 数据结构与算法 / 红黑树

    一.定义 根节点是黑色的. 叶子节点是空的且是黑色的. 任何相邻的节点不能都为红色. 任意节点到其每个叶子节点的路径上,黑色的节点的数量相同. 二.性质 红黑树解决了 AVL 树增.删时耗时过大的问题 ...

  4. 父子结构查询_Java面试准备(5)之数据结构与算法——红黑树

    欢迎点赞评论+关注~~~~~~~ 如上图,二叉查找树极端情况下可能会变成一个单链表,这种查询时间复杂度就变成O(n)了,红黑树在二叉查找树的基础上进行了自平衡. 1.原理分析 如上图,红黑树具有以下特 ...

  5. 红黑树(Red Black Tree)详解

    红黑树 红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组. 红黑树是在1972年由Rudolf Bayer发明的,当时被称为 ...

  6. 红黑树(Red Black Tree)超详细解析

    红黑树详解 什么是红黑树? ​ 红黑树,是一种二叉搜索树的特化,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black. 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确 ...

  7. 红黑树(Red–black tree)

    一.红黑树的概念: 在计算机科学中,红黑树是一种自平衡二叉搜索树.每个节点存储一个表示"颜色"("红"或"黑")的额外位,用于确保树在插入和 ...

  8. 数据结构之「红黑树」

    红黑树 红黑树(Red–black tree)是一种自平衡二叉查找树.红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色. 红黑树的特性: 1.节点要么是红色要么就是黑色,不能没有颜色. 2 ...

  9. 数据结构 - 学习笔记 - 红黑树

    数据结构 - 学习笔记 - 红黑树 定义 简介 知识点 1. 结点属性 2. 前驱.后继 3. 旋转 查找 插入 父结点为黑色 父结点为红色 1. 有4种情形只需要变色(对应234树4结点) 1.1. ...

最新文章

  1. 用 Python 制作可视化报表,这也太快了!
  2. 新浪博客服务器维护,服务器安全维护
  3. 1.3 计划需求控制
  4. SQL 进行数据的更新和插入
  5. vue 出现Elements in iteration expect to have 'v-bind:key' directives
  6. Lost HTML Intellisense within ASP.NET AJAX Controls
  7. Linux基础命令---ab测试apache性能
  8. python导入同一文件夹下的类_Python模块导入机制与规范
  9. Java面向对象之成员变量的初始值、object类与对象
  10. 力荐iReaper/as a Weekend Systems Admin...为何我从来没用过GHOST?
  11. gnu nano显卡测试软件,买不到了 篇四:站内首发——RX VEGA NANO评测
  12. Flux、Mono、Reactor 实战(史上最全)
  13. 互联网中的裂变是什么?
  14. CVE-2022-28512 Fantastic Blog CMS 存在SQL注入漏洞
  15. ios13 微信提示音插件_ios13微信提示音如何进行更改
  16. 模板的完全特例化和部分特例化
  17. java HttpURLConnection类的disconnect方法与http长连接
  18. Tapd 、Jira、Tower对比
  19. 【XSS-4】waf绕过与修复
  20. 各类数据库驱动包列表下载

热门文章

  1. SpringBootAdmin项目创建
  2. 函数的不同的调用方式 函数也是对象 数组的函数调用 apply和call方法的使用
  3. ios上编译c语言的app,iOS App编译流程
  4. 1984年王安微型计算机,王安简:今年中科大少年班最小的学生
  5. =在php中,PHPExcel在PHP7中,save(‘php://output’)出现ERR_INVALID_RESPONSE错误的解决方法...
  6. 简述中断处理的6个步骤_实用!处理电机振动的11个常用步骤
  7. java版spring cloud+spring boot+redis多租户社交电子商务平台(三)SpringBoot用JdbcTemplates访问Mysql...
  8. glyphicon halflings regular ttf 报错
  9. MyBatis整合Spring的实现(2)
  10. Linux系统任务计划(at、crontab)的使用方法