数据结构——红黑树

news/2024/11/25 21:18:06/

红黑树

  • 概念与性质
  • 树节点的定义
  • 插入
  • 红黑树的验证
  • 红黑树与AVL树的对比

概念与性质

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

  1. 每个结点不是红色就是黑色 。
  2. 根节点是黑色的 。
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的。(这里的意思是两个红色节点不能连接到一起)
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。(这里的意思是每条路径的黑色节点数形同)
  5. 每个叶子结点都是黑色的。(此处的叶子结点指的是空结点,也可以叫做NIL结点)

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

我们来考虑最好和最坏的情况:
最好:全都是黑节点,这是满二叉树。时间复杂度:O(logN)
在这里插入图片描述
最坏:最不平衡,但是又不破坏性质,也就是比最好情况多找一倍的量,2 logN,这个量对于计算机而言是非常容易的。时间复杂度:O(logN)
在这里插入图片描述
对于这两种情况,如果其中一条路径添加黑结点,那么其他路径也要添加黑结点,如果是最坏的情况22号结点添加了一个红色结点是不允许的,所以只要遵循红黑树的性质就不会右最长路径超过最短路径二倍的问题。

这棵树和AVL树不同的是没有平衡因子,多了一个颜色变量。
相比较于AVL树,红黑树的旋转更少一些,AVL总是要旋转,也是会影响效率的。

树节点的定义

enum Color//利用枚举来给红黑树配色
{RED,BLACK
};
template<class K, class V>
struct RBTreeNode
{RBTreeNode(const T& data):_data(data), _color(RED)//这里一定要给红色,如果给黑色其他路径就要涉及到也要加黑色结点,更加麻烦,_left(nullptr),_right(nullptr),_parent(nullptr){}RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;pair<K, V> _data;Color _color;//结点的配色
};

插入

插入的第一步和搜索二叉树的一样,先找到合适的位置。
第二步就是判断是否符合红黑树的性质,因为插入的新节点是红色的,所以分为以下几个情况考虑。
图中的g代表grandfather,p是parent,u是uncle,c是cur。
情况一: cur为红,p为红,g为黑,u存在且为红。
在这里插入图片描述
c为新增节点,p为红色,u也为红色,g点为黑色,这个时候应该将p和u变成黑色,g变成红色。
在这里插入图片描述
第一种情况,无论c插入的地方是p的子树还是u的子树都无所谓。
这里还要注意,如果g结点的上面还是红色结点,这里就需要继续向上调整。
这个时候就要将g赋值给c,p等于c的parent指向的结点。

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑。
在这里插入图片描述
在这里插入图片描述
首先对g右单旋,然后将p变为红色,g变为黑色。
这个情况无论是对于局部树还是根节点都不会使上面的发生任何变化,因为原来g结点的位置变成了p结点,但是颜色还是黑色。
这里就算u结点什么也没有,也是按照旋转+变色的方式来解决问题。
情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑
在这里插入图片描述
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;
相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
则转换成了情况2

其实这三种情况看下来之后我们发现,最重要的是叔叔节点。
代码实现

	bool Insert(const pair<K, V>& data){if (_root == nullptr){_root = new Node(data);_root->_color = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_data.first > data.first){parent = cur;cur = cur->_left;}else if (cur->_data.first < data.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(data);//这里默认是红色结点if (parent->_data.first > data.first){cur->_parent = parent;parent->_left = cur;}else if (parent->_data.first < data.first){cur->_parent = parent;parent->_right = cur;}//如果父节点为空就代表cur是根节点,没必要调整了//还要判断cur结点是否与父节点的颜色均为红色while (parent && parent->_color == RED){Node* grandfather = parent->_parent;//祖父结点if (parent == grandfather->_left)//新增结点在祖父左{Node* uncle = grandfather->_right;//情况一if (uncle && uncle->_color == RED)//这里不要忘记验证uncle的存在{parent->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;//最后让cur等于祖父结点的位置parent = cur->_parent;}else{if (parent->_left == cur)//情况二{RotateR(grandfather);//右单旋grandfather->_color = RED;parent->_color = BLACK;}else if (parent->_right == cur)//情况三{RotateL(parent);//左单旋RotateR(grandfather);//右单旋cur->_color = BLACK;grandfather->_color = RED;}break;//第二种和第三种情况变完之后因为最上面的组节点变为黑,所以这里跳出循环}}else//新增结点在祖父右{Node* uncle = grandfather->_left;if (uncle && uncle->_color == RED)//情况一{uncle->_color = BLACK;parent->_color = BLACK;grandfather->_color = RED;cur = grandfather;parent = cur->_parent;}else {if (cur == parent->_right)//情况二{RotateL(grandfather);grandfather->_color = RED;parent->_color = BLACK;}else if (cur == parent->_left)//情况三{RotateR(parent);RotateL(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break;}}}_root->_color = BLACK;return true;}void RotateL(Node* parent)//左单旋{Node* pparent = parent->_parent;Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;parent->_parent = subR;if (pparent){if (pparent->_left == parent)pparent->_left = subR;elsepparent->_right = subR;}else{_root = subR;}subR->_parent = pparent;}void RotateR(Node* parent)//右单旋{Node* pparent = parent->_parent;Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;parent->_parent = subL;if (pparent){if (pparent->_left == parent)pparent->_left = subL;elsepparent->_right = subL;}else{_root = subL;}subL->_parent = pparent;}

红黑树的验证

红黑树的验证需要去检验不能是两个连续的红色结点,并且每条路径上的黑色节点相同。
这里解决的方法是添加两个参数,一个参数用来计算每条路径上的黑色结点,另外一个值用来储存某一条路径的黑色结点的个数。

	bool _IsBalanceTree(Node* root, int k, int sum)//验证{if (root == nullptr){if (k == sum)//这里代表当前路径点和最左边的路径点相同return true;else{cout << "每条路径上黑色结点数量不同" << endl;}return false;}if (root->_color == BLACK)k++;if (root->_parent && root->_parent->_color == RED && root->_color == RED){cout << root->_parent->_data.first << endl;return false;}return _IsBalanceTree(root->_left, k, sum)&&_IsBalanceTree(root->_right, k, sum);}

在这里插入图片描述

红黑树与AVL树的对比

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


http://www.ppmy.cn/news/49800.html

相关文章

GPT3.5, InstructGPT和ChatGPT的关系

GPT-3.5 GPT-3.5 系列是一系列模型&#xff0c;从 2021 年第四季度开始就使用文本和代一起进行训练。以下模型属于 GPT-3.5 系列&#xff1a; code-davinci-002 是一个基础模型&#xff0c;非常适合纯代码完成任务text-davinci-002 是一个基于 code-davinci-002 的 InstructG…

基于贝叶斯优化CNN-LSTM混合神经网络预测(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

51.网页设计规则#1_字体设计

一些概念 字体排版 排版是安排字体的艺术和技术&#xff0c;以使书面语言在显示时清晰、可读和吸引人。&#xff08;来自维基百科翻译&#xff09; serif字体和sans-serif字体 serif字体 ● 创造一个传统/经典的外观和感觉 ● 体现出可信度 ● 适合长文本 sans-serif字体 …

scrapy实践-02

双师demo ptpress.com.cn/shopping/index 解析每一首歌 <ul class"f-hide"><li><a href"/song?id2037945324">芯房</a></li><li><a href"/song?id2037926385">知足</a></li><li>…

69. x 的平方根

给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。 由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。 注意&#xff1a;不允许使用任何内置指数函数和算符&#xff0c;例如 pow(x, 0.5) 或者 x ** 0.5 。 示例 1&#xff1…

( “树” 之 BST) 669. 修剪二叉搜索树 ——【Leetcode每日一题】

二叉查找树&#xff08;BST&#xff09;&#xff1a;根节点大于等于左子树所有节点&#xff0c;小于等于右子树所有节点。 二叉查找树中序遍历有序。 669. 修剪二叉搜索树 给你二叉搜索树的根节点 root &#xff0c;同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树&…

MySQL(三)

MySQL - 事务/索引 1. 数据库级别的MD5加密2. 事务2.1 事务原则&#xff1a;ACID2.2 事务并发导致的问题2.3 隔离级别2.4 执行事务的过程 3. 索引3.1 索引的分类3.2 索引的使用3.3 测试索引3.4 索引原则 4. explain关键字5. 权限管理和备份5.1 用户管理5.2 数据库备份 6. 三大范…

JDK多版本配置及切换版本不生效问题解决

一、准备工作 首先你要有多个版本的jdk&#xff0c;如果没有请移至 https://www.oracle.com/java/technologies/downloads/ 下载&#xff0c;具体下载方法可参考&#xff1a;Java同学入职环境安装全讲解 二、配置环境变量 在环境变量中配置多个JAVA_HOME&#xff0c;我这里…