数据结构篇十:红黑树

文章目录

  • 前言
  • 1. 红黑树的概念
  • 2. 红黑树的性质
  • 3. 红黑树节点的定义
  • 4. 红黑树的插入
    • 4.1 情况一: cur为红,p为红,g为黑,u存在且为红
    • 4.2 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑。
      • 4.2.1 u不存在
      • 4.2.2 u存在且为黑
    • 4.3 情况三
  • 5. 红黑树的验证
    • 5.1 检测其是否满足二叉搜索树
    • 5.2 检测其是否满足红黑树的性质
  • 6. 红黑树的删除
  • 7. 红黑树与AVL树的比较
  • 8. 代码实现
    • 8.1 RBTree.h
    • 8.2 Test.cpp
  • 9. 总结

前言

  红黑树是解决单支树问题的另一种解决方法,它相比较AVL树减少了调整的次数,AVL是一格绝对平衡的树,而红黑树只要求最长路径不超过最短路径的二倍,相比较大大减少了调整次数。在实际中更多的也是使用红黑树,就比如后面的map和set,我们就是以红黑树进行封装的。

1. 红黑树的概念

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

2. 红黑树的性质

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

思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

  按照上面的性质,最短路径应该是全为黑色节点,而在每条路径黑色节点数量相同并且不难出现连续红色节点的的情况下,最长路径应该是一红一黑,红黑相间的方式,所以最长路径是不会超过最短路径的二倍的,

3. 红黑树节点的定义

enum Colour
{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;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED){}
};

  思考:在节点的定义中,为什么要将节点的默认颜色给成红色的?

  这是因为有一个性质是要求每条路径的黑色节点数目相同,如果我们将新插入节点的颜色默认给成黑色,就会影响全部路径,那么就会在全局破坏这一性质。而如果我们将节点颜色给成红色,我们可能会破坏不能出现连续的红色节点这一性质,也只是影响了这一条路径,也仅仅只是在局部破坏了红黑树的性质,相比较前一种更容易进行处理。

4. 红黑树的插入

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

  1. 按照二叉搜索的树规则插入新节点
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){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (parent->_kv.first > kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}//调整…………
  • 检测新节点插入后,红黑树的性质是否造到破坏
      因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论。(约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点)

4.1 情况一: cur为红,p为红,g为黑,u存在且为红

  • 情况一: cur为红,p为红,g为黑,u存在且为红

在这里插入图片描述
  解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

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

  • 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑。
    在这里插入图片描述

  • p为g的左孩子,cur为p的左孩子,则进行右单旋转。

  • p为g的右孩子,cur为p的右孩子,则进行左单旋转。

  • p、g变色–p变黑,g变红。

4.2.1 u不存在

  光看上面的图有人可能会不理解为什么还需要旋转,我们来单独拿出一个例子:
在这里插入图片描述
  当u不存在时,它是违反最长路径超过最短路径的二倍的,因此需要先进行旋转再变色。

4.2.2 u存在且为黑

  先新插入一个节点,目前是情况一,直接变色处理就可以了。
在这里插入图片描述
  处理完后就是此时所讲的情况二: cur为红,p为红,g为黑,u存在且为黑。我们发现此时违反了最长路径不能超过最短路径的二倍,因此需要先旋转,再变色。
在这里插入图片描述
  此时再回归到我们一开始的图解,上下图结合,也就是cur的孩子就是下图中的a
在这里插入图片描述
  这样就可以完美的解决问题了。

4.3 情况三

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

在这里插入图片描述

  • p为g的左孩子,cur为p的右孩子,则针对p做左单旋转。
  • p为g的右孩子,cur为p的左孩子,则针对p做右单旋转。
  • 则转换成了情况2。

  情况三的原理与情况二一致,只不过是换成了双旋的问题。(记忆窍门:单旋是的情况是一条直线,双旋的情况是一条折线)。
在这里插入图片描述

  情况二就是新插入节点是属于直线的情况,只需要旋转一次,情况三是新插入节点属于折线的情况,需要旋转一次变成情况二,再旋转一次解决问题,实际上也就是双旋。(还有一个记忆敲门:左边高往右边旋,右边高往左边旋)
  更具体的情况大家可以像我讲解情况二那样画出详细的图来理解。
  需要格外注意到最后要将根节点改为黑色, 因为在调整过程中有可能将根节点改为红色
  左右子树的旋转在上一篇AVL树中有纤细的讲解,不懂的小伙伴可以去阅读。

5. 红黑树的验证

  红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)。
  2. 检测其是否满足红黑树的性质。

5.1 检测其是否满足二叉搜索树

  写一个中序遍历就可以了。

	void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}

5.2 检测其是否满足红黑树的性质

  最主要的是检验每条路径的黑色节点数目是否一样,是否有连续的红色节点。
  对于检验每条路径的黑色节点数目是否一样,我们可以先遍历一条路径记录黑色节点的数目,再通过递归遍历每条路径记录黑色节点的数目,当遍历到nullptr时说明一条路径遍历完了,再与我们一开始记录的黑色节点数目进行比较,相同就返回true,不同就返回false。(注意:传参时需要传值传递,不能引用传递,否则在统计黑色节点数目时,左子树的黑色节点数目会累加到右子树上
  对于是否有连续的红色节点,在递归时对于一个节点,我们如果是拿这个节点与它的孩子节点比较会很麻烦,因为要比较两个。我们不如换个思路,与该节点的父亲节点比较,如果该节点为红色,它的父亲也为红色,就返回false。

bool Check(Node* root, int blackNum, const int refVal)
{if (root == nullptr){//cout << balcknum << endl;if (blackNum != refVal){cout << "存在黑色节点数量不相等的路径" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "有连续的红色节点" << endl;return false;}if (root->_col == BLACK){++blackNum;}return Check(root->_left, blackNum, refVal)&& Check(root->_right, blackNum, refVal);
}bool IsBalance()
{//树为空认为符合红黑树的性质if (_root == nullptr)return true;//根节点必须为黑if (_root->_col == RED)return false;//记录黑色节点数目int refVal = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)refVal++;cur = cur->_left;}int blackNum = 0;return Check(_root,blackNum,refVal);
}

6. 红黑树的删除

  红黑树的删除本节不做讲解,有兴趣的小伙伴可参考:《算法导论》或者《STL源码剖析》。

7. 红黑树与AVL树的比较

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

8. 代码实现

8.1 RBTree.h

#pragma once
#include<iostream>
#include<vector>
using namespace std;enum Colour
{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;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED){}
};template<class K,class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public: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){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (parent->_kv.first > kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}while (parent && parent->_col == RED){Node* uncle = nullptr;Node* grandparent = parent->_parent;if (parent == grandparent->_left){uncle = grandparent->_right;}else{uncle = grandparent->_left;}//叔叔存在且为红if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandparent->_col = RED;cur = grandparent;parent = cur->_parent;}//叔叔不存在或者存在且为黑else{if (parent == grandparent->_left && cur == parent->_left){//右旋RotateR(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else if (parent == grandparent->_left && cur == parent->_right){//先左旋,再右旋RotateL(parent);RotateR(grandparent);cur->_col = BLACK;grandparent->_col = RED;}else if (parent == grandparent->_right && cur == parent->_right){//左旋RotateL(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else{//先右旋,再左旋RotateR(parent);RotateL(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}_root->_col = BLACK;return true;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* ppNode = parent->_parent;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;parent->_parent = subR;if (parent == _root){subR->_parent = nullptr;_root = subR;}else{if (parent == ppNode->_left){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* ppNode = parent->_parent;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){subL->_parent = nullptr;_root = subL;}else{if (parent == ppNode->_left){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}bool Check(Node* root, int blackNum, const int refVal){if (root == nullptr){//cout << balcknum << endl;if (blackNum != refVal){cout << "存在黑色节点数量不相等的路径" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "有连续的红色节点" << endl;return false;}if (root->_col == BLACK){++blackNum;}/*return Check(_root->_left, blackNum, refVal)&& Check(_root->_right, blackNum, refVal);*/return Check(root->_left, blackNum, refVal)&& Check(root->_right, blackNum, refVal);}bool IsBalance(){if (_root == nullptr)return true;if (_root->_col == RED)return false;int refVal = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)refVal++;cur = cur->_left;}int blackNum = 0;return Check(_root,blackNum,refVal);}private:Node* _root = nullptr;
};

8.2 Test.cpp

int main()
{//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };RBTree<int, int> t;for (auto e : a){t.insert(make_pair(e, e));}t.InOrder();//cout << t.IsBalance() << endl;return 0;
}int main()
{const int N = 1000;vector<int> v;v.reserve(N);//srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);}//size_t begin2 = clock();RBTree<int, int> t;for (auto e : v){if (e == 506){int i = 0;}t.insert(make_pair(e, e));cout << "insert:" << e << "->" << t.IsBalance() << endl;}//size_t end2 = clock();//cout << "Insert:" << end2 - begin2 << endl;cout << t.IsBalance() << endl;return 0;
}

9. 总结

  红黑树比AVL树更加复杂一些,关于二叉树这部分的内容我建议还是多多画图来加深自己的理解,忌讳眼高手低。希望大家都能有所收获。
  如果大家发现有什么错误的地方,可以私信或者评论区指出喔。我会继续深入学习C++,希望能与大家共同进步,那么本期就到此结束,让我们下期再见!!觉得不错可以点个赞以示鼓励!!

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

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

相关文章

单源最短路的建图方式

1129. 热浪 - AcWing题库 这道题可以有三种方法来做&#xff0c;朴素版的dijkstra、堆优化版的dijkstra和spfa算法 &#xff08;1&#xff09;spfa算法 这里的队列用循环队列&#xff0c;而不是像模板那样用普通队列是因为它的队列长度不确定 import java.util.*;public class…

【论文阅读】基于图像处理和卷积神经网络的板式换热器气泡识别与跟踪

Bubble recognizing and tracking in a plate heat exchanger by using image processing and convolutional neural network 基于图像处理和卷积神经网络的板式换热器气泡识别与跟踪 期刊信息&#xff1a;International Journal of Multiphase Flow 2021 期刊级别&#xff1a;…

03、MongoDB -- MongoDB 权限的设计

目录 MongoDB 权限的设计演示前准备&#xff1a;启动 mongodb 服务器 和 客户端 &#xff1a;1、启动单机模式的 mongodb 服务器2、启动 mongodb 的客户端 MongoDB 权限的设计1、MongoDB 的每个数据库都可以保存用户&#xff0c;不止admin数据库可以保存用户。2、保存用户的数据…

JVM的内存区域划分

目录 1.什么是JVM 2.JVM内存区域划分 2.1各区域详解 2.2经典笔试题: 1.什么是JVM JVM也称为Java虚拟机,它是Java代码的运行环境,Java属于半编译半解释形的语言.它的运行环境在虚拟机上,而不是物理设备.Java这么设定主要是为了跨平台,即一套代码,多处使用.我们只需要编译出一…

技巧 文本编辑器 B列每一行数据换行合并到A列中

一. 需求背景 ⏹A列是我们制作的日文版歌词&#xff0c;B列是中文版译文歌词 现在想让B列的每一行歌词&#xff0c;按下图箭头所示插入到A列的每一行后面 二. 通过文本编辑器的替换功能解决 将Excel中的A和B列的数据复制粘贴到文本编辑器中Excel中的列和列之前是通过Tab来分隔…

Matlab|【免费】基于合作博弈的综合能源系统利益分配优化调度

目录 主要内容 部分代码 结果一览 下载链接 主要内容 该程序实现的模型为综合能源系统利益分配优化调度&#xff0c;采用合作博弈方法&#xff0c;模型针对IES系统的P2G、电解槽、甲烷反应器、储氢罐、CHP和燃气锅炉等设备进行建模&#xff0c;实现基于合作博弈的…

环形链表详解(让你彻底理解环形链表)

文章目录 一.什么是环形链表&#xff1f;二.环形链表的例题&#xff08;力扣&#xff09; 三.环形链表的延伸问题 补充 一.什么是环形链表&#xff1f; 环形链表是一种特殊类型的链表数据结构&#xff0c;其最后一个节点的"下一个"指针指向链表中的某个节点&#xff…

代码随想录【数组】 ---- 二分查找

代码随想录【数组】 ---- 二分查找 704.二分查找方法一&#xff1a;二分查找 35.搜索插入位置方法一&#xff1a;二分查找 34.在排序数组中查找元素的第一个和最后一个位置方法一&#xff1a;二分查找 69.x的平方根方法一&#xff1a;袖珍计算器方法二&#xff1a;二分查找方法…

抖音小店的产品价格怎么设置?都需要什么价位的产品?

大家好&#xff0c;我是电商花花。 做抖音小店&#xff0c;一个合理的商品的价格也可以说是非常重要的&#xff0c;价格合理才会吸引到用户这购买。 可能说到价格&#xff0c;很多人第一反应认为随便定就可以了&#xff0c;其实定价是很复杂了&#xff0c;定价定多少&#xf…

一份简单的前端开发指南

文章目录 一、HTML1、表格2、常见标签3、行内、块级4、行内块级元素 二、CSS1、三种样式2、链接样式3、浮动4、清除浮动5、伪类&#xff0c;伪元素6、position7、后代选择器8、弹性布局 三、JavaScripts1、null和undefined的区别2、var let const3、原生数据类型4、双等和三等5…

Vue3 条件渲染 v-if

v-if 指令&#xff1a;用于控制元素的显示或隐藏。 执行条件&#xff1a;当条件为 false 时&#xff0c;会将元素从 DOM 中删除。 应用场景&#xff1a;适用于显示隐藏切换频率较低的场景。 语法格式&#xff1a; <div v-if"数据">内容</div> 基础用…

KubeSphere平台安装系列之三【Linux多节点部署KubeSphere】(3/3)

**《KubeSphere平台安装系列》** 【Kubernetes上安装KubeSphere&#xff08;亲测–实操完整版&#xff09;】&#xff08;1/3&#xff09; 【Linux单节点部署KubeSphere】&#xff08;2/3&#xff09; 【Linux多节点部署KubeSphere】&#xff08;3/3&#xff09; **《KubeS…