C++红黑树

server/2024/9/22 11:09:50/

目录

一、红黑树的概念

二、红黑树的性质

三、红黑树节点的定义

四、红黑树的插入

1. 按照二叉搜索的树规则插入新节点

2. 检测新节点插入后,红黑树的性质是否造到破坏

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

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

五、红黑树的验证

六、红黑树和AVL树的比较


一、红黑树的概念

        红黑树,是一种二叉搜索树,但是在每个节点上增加了一个存储位表示节点的颜色,可以是RED或者BLACK。通过对任何一条从根到叶子的路径上各个节点颜色的某种限制,红黑树保证了没有一条路径会比最短路径长出两倍,所以红黑树是一种接近平衡的搜索二叉树。

二、红黑树的性质

1.每个节点不是红色就是黑色

2.根节点是黑色的

3.如果一个节点是红色的,那么他的两个孩子节点是黑色的(即不能有连续的红色节点)

4.对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含一样数量的黑色节点

5.每个叶子节点都是黑色的(这里的叶子节点指的是空节点,也叫NIL节点)

三、红黑树节点的定义

	enum Color{RED,BLACK};template<class K,class V>struct RBTreeNode{typedef RBTreeNode<K, V> Node;Node* _left;Node* _right;Node* _parent;pair<K, V> _kv;Color _col;//构造函数RBTreeNode(const pair<K,V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED){}};

        为什么红黑树的节点默认是红色呢?因为如果插入黑色节点,会违反规则4,但是插入红色节点,只有可能影响规则3(不能有连续的红色节点),调整起来比较方便。

四、红黑树的插入

1. 按照二叉搜索的树规则插入新节点

template<class ValueType>
class RBTree
{//……
bool Insert(const ValueType& data)
{
PNode& pRoot = GetRoot();
if (nullptr == pRoot)
{
pRoot = new Node(data, BLACK);
// 根的双亲为头节点
pRoot->_pParent = _pHead;_pHead->_pParent = pRoot;
}
else
{
// 1. 按照二叉搜索的树方式插入新节点// 2. 检测新节点插入后,红黑树的性质是否造到破坏,
//  若满足直接退出,否则对红黑树进行旋转着色处理
}
// 根节点的颜色可能被修改,将其改回黑色
pRoot->_color = BLACK;
_pHead->_pLeft = LeftMost();
_pHead->_pRight = RightMost();
return true;
}
private:
PNode& GetRoot(){ return _pHead->_pParent;}private:
PNode _pHead;

2. 检测新节点插入后,红黑树的性质是否造到破坏

        因为新节点的默认颜色是红色,因此,:如果其父亲节点是黑色,就没有违反红黑树的性质,则不需要调整,可以直接插入。但是当父亲节点为红色的时候,则违反了性质三(不能有连续的红色节点),此时需要对红黑树进行分类讨论。

        约定:cur为当前节点,p为父节点,g为祖父节点,uncle为叔叔节点

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

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。

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

        但是对于情况二:我们需要分类讨论,因为cur在parent的左边和右边/parent在祖父的左边和右边所产生的旋转是不同的,一共需要分成4类来讨论

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);return true;}else{//找到要插入的位置Node* cur = _root;Node* parent = cur->_parent;while (cur!=nullptr){if (kv.first<cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first>cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first==cur->_kv.first){return false;}}//cur是连接到parent的左边还是右边呢cur = new Node(kv);if (cur->_kv.first<parent->_kv.first){parent->_left = cur;cur->_parent = parent;}else if (cur->_kv.first>parent->_kv.first){parent->_right = cur;cur->_parent = parent;}//根据红黑树的规则进行变色+旋转//只有当父亲是红色且存在,才需要处理,因为父亲是黑色对上面没有影响可以直接插入while (parent!=nullptr&&parent->_col==RED){Node* grandparent = parent->_parent;//parent==grandparent->_leftif (parent==grandparent->_left){Node* uncle = grandparent->_right;//叔叔存在且为红色if (uncle!=nullptr&&uncle->_col==RED){//变色parent->_col = uncle->_col = BLACK;grandparent->_col = RED;//grandparent给cur继续向上调整cur = grandparent;parent = grandparent->_parent;}//叔叔不存在或者存在且为黑色else{if (cur==parent->_left){//右单旋_RotateR(grandparent);//变色grandparent->_col = RED;parent->_col = BLACK;}else{//双旋_RotateL(parent);_RotateR(grandparent);cur->_col = BLACK;grandparent->_col = RED;}//因为走到else结束后,parent右一种情况是是黑色,有一种情况是红色,但是都以及调整好了,//不需要再往上调整,所以直接breakbreak;}}//parent==grandparent->_rightelse{Node* uncle = grandparent->_left;if (uncle!=nullptr&&uncle->_col==RED){//变色uncle->_col = parent->_col = BLACK;grandparent->_col = RED;//继续往上处理cur = grandparent;parent = grandparent->_parent;}else{if (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 _RotateR(Node* parent){//记录两个重要节点Node* subL = parent->_left;Node* subLR = subL->_right;//连接parent->_left = subLR;//如果subL的右边为空,空不能解引用,需要注意一下if (subLR->_right != nullptr){subLR->_parent = parent;}subL->_right = parent;//处理根的问题Node* grandparent = parent->_parent;parent->_parent = subL;subL->_parent = grandparent;//如果原本parent是根,则让_root=subLif (parent == _root){_root = subL;subL->_parent = nullpptr;}//如果parent是子树,则连接subL和grandparentelse{if (grandparent->_right == parent){grandparent->_right = subL;}else if (grandparent->_left == parent){grandparent->_left = subL;}}}//左单旋void _RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;Node* grandparent = parent->_parent;parent->_parent = subR;if (subRL != nullptr){subRL->_parent = parent;}//处理根的问题if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (parent == grandparent->_right){grandparent->_right = subR;}else if (parent == grandparent->_left){grandparent->_left = subR;}}}private:Node* _root = nullptr;};
}

五、红黑树的验证

        红黑树的验证检测分为两步:

(1)检测其是非满足二叉搜索树的规则(中序是否为有序)

(2)检测其是否满足红黑树的性质

bool isBalance(){if (_root==nullptr){return true;}if (_root->_col==RED){return false;}//求参考值int refval = 0;Node* cur = _root;while (cur!=nullptr){if (cur->_col==BLACK){refval++;}cur = cur->_left;}return Check(_root, 0, refval);}//检查颜色是否符合规则和黑色节点的数量是否相同//blacknum是根节点到当前节点路径上的黑色节点数量bool Check(Node* root,int blacknum,const int refVal){//走到空的时候说明该路径已经走完了,比对balcknum的值if (root==nullptr){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);}

六、红黑树和AVL树的比较

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

        红黑树常常因为其高效的性能,而被用到map/set等数据结构中。


http://www.ppmy.cn/server/120258.html

相关文章

Redis中Hash(哈希)类型的基本操作

文章目录 一、 哈希简介二、常用命令hsethgethexistshdelhkeyshvalshgetallhmgethlenhsetnxhincrbyhincrbyfloathstrlen 三、命令小结四、哈希内部编码方式五、典型应用场景六、 字符串&#xff0c;序列化&#xff0c;哈希对比 一、 哈希简介 几乎所有的主流编程语言都提供了哈…

35. 模型材质和几何体属性

本文章给大家介绍模型对象的几何体.geometry和材质属性.material。 浏览器控制台查看对象和属性 浏览器控制打印模型对象mesh&#xff0c;可以展开对象&#xff0c;查看对象的几何体.geometry和材质属性.material。 const mesh new THREE.Mesh(geometry, material); consol…

OpenCV-直方图

文章目录 一、直方图1.含义2.参数解释 二、代码应用1.灰度图像的直方图2.绘制灰度图像直方图3.彩色图像直方图 一、直方图 1.含义 在OpenCV中&#xff0c;直方图是一种非常重要的工具&#xff0c;用于表示图像中像素强度的分布情况。直方图可以帮助我们了解图像的亮度、对比度…

简单水印通过python去除

简单水印通过python去除 先看效果&#xff0c;如果效果不是你需要的就可以不用浪费时间。 注意&#xff1a;这种主要还是对应的文字在水印上方的情况&#xff0c;同时最好不要有渐变水印否则可能最后输出的图片的水印还会有所残留&#xff0c;不过还是学习使用&#xff0c;相信…

MySQL之复合查询与内外连接

目录 一&#xff1a;基本查询 二:多表查询 三:自连接 四:子查询 1.单行子查询 2.多行子查询 3 多列子查询 4.在from子句中使用子查询 5. 合并查询 五&#xff1a;表的内外连接 1.内连接 2.外连接 一&#xff1a;基本查询 (1)查询工资高于500或岗位为MANAGER的雇员…

Semaphore UI --Ansible webui

1、安装python python下载地址 https://www.python.org/downloads/ 选好版本下载 wget https://www.python.org/ftp/python/3.11.9/Python-3.11.9.tar.xz安装编译工具 sudo dnf groupinstall "Development Tools"安装依赖包 dnf install bzip2-devel ncurses-deve…

ElementUI 快速入门:使用 Vue 脚手架搭建项目

文章目录 一 . ElementUI 的基本安装1.1 通过 Vue 脚手架创建项目1.2 在 vue 脚手架中安装 ElementUI1.3 编写页面 ElementUI 是 Vue.js 的强大 UI 框架&#xff0c;让前端界面开发变得简单高效。本教程将带你从安装到实战&#xff0c;快速掌握 ElementUI 的核心技巧。 核心内容…

【SQL】NVL函数的用法和MySQL中有什么不同

一、在Oracle数据库中&#xff0c;NVL函数的用法和MySQL中有什么不同&#xff1f; 在Oracle数据库中&#xff0c;NVL 函数用于将 NULL 值替换为指定的值。如果第一个参数不是 NULL&#xff0c;NVL 函数返回第一个参数的值&#xff1b;如果第一个参数是 NULL&#xff0c;它返回…