c++学习之红黑树

目录

一.什么是红黑树

二.红黑树的性质

三.红黑树的实现

节点定义:

1.insert

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

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

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

判断平衡

高度

 迭代器

四,红黑树的封装

map的封装

set封装


一.什么是红黑树

红黑树(英文:Red–black tree)是一种自平衡二叉查找树,在计算机科学中被广泛应用,特别用于实现关联数组。它的名字源于1978年由利奧尼達斯·J·吉巴斯和罗伯特·塞奇威克所提出的红黑树概念。

与AVL树不同,红黑树在每个节点上增加一个存储位表示节点的颜色(可以是RED或BLACK),通过多任意一条从根到叶子的路径上节点着色方式的限制,红黑树以确保没有一条路径会比其他路径长出两倍,因此是平衡的。相对于AVL树的严格平衡,红黑树近似平衡,但相对在建树的代价时较小一点。

如下就是一个红黑树:

二.红黑树的性质

我们大概可以用五点来总结红黑树的性质:

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

 总结来说,一个红黑树首先节点只有红黑其中一种颜色,根必须时黑的,不能有连续红节点,每条路径有相同的黑节点的个数,空叶子为黑。

对于第五条,此处的叶子节点不是我们平常说的叶子节点,而是再往下一层的空节点为叶子节点,且必须为黑。

那么为什么满足上述五条,最长的子路径就不会超过最短子路径的二倍呢?

由上述特点可知,形成的最短路径时,当节点只有黑时,最短,形成的树最为平衡。

形成最长路径时,节点此时应该为一黑一红交替,此时路径应是最长。

但是由于红节点一定比黑节点少,此时最长的一定小于最短的二倍。

可以看到上图,全是黑时,路径最长最短都是3,一黑一红时,最短4,最长7,但不会比最短2倍长。

其次面对红黑树时,我们一定要弄清它的路径的个数(注意空叶子节点):

这里一共是有6条路径的。

三.红黑树的实现

节点定义:

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;enum colour _col;RBTreeNode(const pair<K,V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED){}
};
template<class K, class V>struct RBTree
{
//.......Node*_root;
}

1.insert

首先对于插入,与AVL树一样我们需要对插入之后的二叉树平衡,那么我们该如何去平衡呢?

对于红黑树是通过变色或者以及旋转来达到平衡。

我们再仔细来划分:

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

因为新节点的默认颜色是红色
因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何 性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连 在一起的红色节点,此时需要对红黑树分情况来讨论:
cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点,a,b,c,d,e分别代表五个子树,他们可能为空,可能为一个节点,两个节点链接,等等。
首先粗略的划分,即用抽象图来划分情况:我们先总结出局部处理的规律,再依次向上调整。
对于各类情况,我们需要以uncle节点 来划分。

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

那么如何调整呢?

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
首先,对于a,b,c,d,e五个抽象子树来说,如何找到调整的结局方案,就让五个子树全部为空,即此时的新增节点就是节点cur,可以看到我们需要调整:
而这里的调整方案就是将 将p,u改为黑,g改为红。g改为红是必要的,因为我们不能影响这个局部子树黑节点的个数。

这里我们列举一下当五个抽象子树为一个结点的情况,新增节点插入的演示:

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

情况一的uncle存在,那么当不存在时,或者颜色与情况一相反,就为情况二:

 那么对于情况二,的解决方案是:

如果uncle是黑色:

和情况一思想一样是一(让cur节点的上面两个变黑,g变红),但是到cur这里需要旋转,

pg的左孩子,curp的左孩子,则进行右单旋转;相反, pg的右孩子,curp的右孩子,则进行左单旋转,之后pg变色--p变黑,g变红

这里我们以左左为例

如果uncle是不存在的:
p g 的左孩子, cur p 的左孩子,则进行右单旋转;相反, p g 的右孩子, cur p 的右孩子,则进行左单旋转。
如下:

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

情况三看着和情况二一样,实则不然,这里的左右方向是不一样的。

对于他们处理的方法,还是利用旋转处理转换成情况二。

 

p g 的左孩子, cur p 的右孩子,则针对 p 做左单旋转;相反,
p g 的右孩子, cur p 的左孩子,则针对 p 做右单旋转
则转换成了情况 2
然而实际在操作这里时,直接就是双旋一步到位。
代码实现:
bool insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);cur->_col = RED;//新节点我们默认为红色,由红黑树的性质可知,若为黑节点,//每一条路径的黑色节点都要增加。/*不过新增红色节点需要去变父亲的色*/if (parent->_kv.first < kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}/*先一个个插入,再平衡先从情况一开始当父亲节点的颜色为红色时,需要处理*/while (parent && parent->_col == RED){Node* grandparent = parent->_parent;if (parent == grandparent->_left){Node* uncle = grandparent->_right;if (uncle && uncle->_col == RED){/*变色处理*/parent->_col = BLACK;uncle->_col = BLACK;grandparent->_col = RED;/*继续向上更新*/cur = grandparent;parent = cur->_parent;}else{/*情况二,叔叔不存在或者为黑*/if (cur == parent->_left){/*旋转加变色右单旋*/RotateR(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else{/*cur为parent的right,这里归类到情况三*//*我们不再转换到情况二,直接双旋如果cur是parent的右,左单旋,之后右单旋,在变色(和情况二一样)*/RotateL(parent);RotateR(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}else {/*此时的叔叔在grandparent的左边*/Node* uncle = grandparent->_left;/*还是情况一,变色*/if (uncle && uncle->_col == RED){/*变色处理*/parent->_col = BLACK;uncle->_col = BLACK;grandparent->_col = RED;/*继续向上更新*/cur = grandparent;parent = cur->_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的颜色一定为黑*/_root->_col = BLACK;return true;}
总的来说,在实现的时候,我们分两种大类情况,即当cur分别在parent的左与右时,parent在grandparent的左右时:
直接进行情况一二三分别处理,不过情况二与情况三可以将情况三当作情况二的一个子情况。
仔细发现上述的三种情况,基本上都存在分别在左和右的情况。

判断平衡

bool check(Node* root, int blacknum, const int flag){if (root == nullptr){if (blacknum != flag){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, flag)&& check(root->_right, blacknum, flag);}bool is_Balance(){if (_root == nullptr)return true;if (_root->_col == RED)return false;//参考值int flag = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++flag;}cur = cur->_left;}int blacknum = 0;return check(_root, blacknum, flag);}

高度

int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}

 迭代器

和我们之前实现的链表的迭代器类似,对于二叉树,迭代器本质就是节点指针,其次主要就是++的重载,那么如何去找中序遍历的下一个节点?

对于it指向的节点,右子树不为空,下一个就是右子树的最左节点。

对于it指向的节点,右子树为空,那说明it节点所在的子树都已经访问完了,往上找(孩子是父亲左边的)那个祖先。

这里主要是实现++功能与减减--

//迭代器
//首先迭代器是一个树的节点指针
template<class T>struct __Treeiterator {typedef RBTreeNode<T> Node;typedef __Treeiterator<T> self;Node* _node;__Treeiterator(Node*newnode):_node(newnode){}T& operator*(){return _node->_data;}T*operator->(){return &(_node->_data);}bool operator!=(const self&s){return _node != s._node;}bool operator=(const self& s){return _node = s->_node;}self& operator++(){if (_node->_right){Node*cur = _node->_right;while (cur->_left){cur = cur->_left;}_node = cur;}else{//左为空,找孩子是父亲左边的祖先Node* cur = _node;Node* parent = cur->_parent;while (parent&&cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}self& operator++(int){//先保存thisself tmp(*this);if (_node->_right){Node* cur = _node->_right;while (cur->_left){cur = cur->_left;}_node = cur;}else{//左为空,找孩子是父亲左边的祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return tmp;}elf&  operator--(){//如果左节点不为空if (_node->_left){Node* cur = _node->_left;while (cur->_left){cur = cur->_left;}_node = cur;}else{//左为空,找的祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){//如果有为空,则我就是父亲cur = parent;//更新节点为parent的父亲parent = parent->_parent;}_node = parent;}return *this;}
};

四,红黑树的封装

对于红黑树的封装,就是利用用红黑树封装出set,map.,接口实现基本没变,只是对模板参数,以及类型,返回值统一一下。封装时,采用统一参数与类型模板。

封装后的RBTree的结构变化,以及insert的变化:

enum colour
{RED,BLACK
};
//不再使用k,v表示key与val的类型,直接用pair作为类型参数传入
template<class T>struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode< T>* _right;RBTreeNode<T>* _parent;T _data;enum colour _col;RBTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}
};//增加一个模板参数,来挥去对应的key的类型
template<class K, class T, class keyofT>struct RBTree
{typedef RBTreeNode<T> Node;typedef __Treeiterator<T> iterator;iterator begin(){//begin就是这棵树的最左节点Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);};iterator end(){// end就是这棵树的最右节点的下一个null,结束标志return iterator(nullptr);};
//...............................
pair<iterator, bool> insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(iterator(_root), true);}Node* parent = nullptr;Node* cur = _root;keyofT k;//一个类型while (cur){if (k(cur->_data)< k(data))//把data中的类型取出来{parent = cur;cur = cur->_right;}else if (k(cur->_data) > k(data)){parent = cur;cur = cur->_left;}else{return make_pair(iterator(_root), false);}}cur = new Node(data);cur->_col = RED;//新节点我们默认为红色,由红黑树的性质可知,若为黑节点,//每一条路径的黑色节点都要增加。/*不过新增红色节点需要去变父亲的色*/if (k(parent->_data) < k(data)){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}/*先一个个插入,再平衡先从情况一开始当父亲节点的颜色为红色时,需要处理*/while (parent && parent->_col == RED){Node* grandparent = parent->_parent;if (parent == grandparent->_left){Node* uncle = grandparent->_right;if (uncle && uncle->_col == RED){/*变色处理*/parent->_col = BLACK;uncle->_col = BLACK;grandparent->_col = RED;/*继续向上更新*/cur = grandparent;parent = cur->_parent;}else{/*情况二,叔叔不存在或者为黑*/if (cur == parent->_left){/*旋转加变色右单旋*/RotateR(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else{/*cur为parent的right,这里归类到情况三*//*我们不再转换到情况二,直接双旋如果cur是parent的右,左单旋,之后右单旋,在变色(和情况二一样)*/RotateL(parent);RotateR(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}else {/*此时的叔叔在grandparent的左边*/Node* uncle = grandparent->_left;/*还是情况一,变色*/if (uncle && uncle->_col == RED){/*变色处理*/parent->_col = BLACK;uncle->_col = BLACK;grandparent->_col = RED;/*继续向上更新*/cur = grandparent;parent = cur->_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的颜色一定为黑*/_root->_col = BLACK;return make_pair(iterator(_root), true);}private:Node* _root=nullptr;
};

map的封装


我们对于RBTree,对于map传pair,这里是有两个值的,但是我们将pair当作一个类型参数传入,但是在比较是pair自己的比较不符合我们的要求,对于比较我们一般通过仿函数来实现这里呢我们直接增加一个模板参数表示 map的 T类型。
 

namespace space
{template<class K, class V> struct map{//通过仿函数确定pair中我们需要的数据,即pair中的firststruct mapKeyofT{const K& operator()(const pair<K, V>& key){return key.first;}};typename typedef RBTree<K, pair<K, V>, mapkeyofT>::iterator  iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}pair<iterator, bool> insert(const pair<K,V>& key){return _t.insert(key);}private:RBTree<K, pair<K, V>, mapkeyofT> _t;//通过这种方式传递我们key的类型};
}

set封装

set这里的setoft只是为了是大家同意。

namespace space
{template<class K> struct set{public://对于set里的仿函数意思一下,你给我这个类型我返回你这个类型struct setKeyofT{const K& operator()(const K&key ){return key;}};typename typedef RBTree<K, K, setKeyofT>::iterator  iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}pair<iterator,bool> insert(const K& key){return _t.insert(key);}private:RBTree<K,  K, setKeyofT> _t;};
}

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

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

相关文章

V10服务器安装virt-manage

kvm是什么 KVM(Kernel-based Virtual Machine, 即内核级虚拟机) 是一个开源的系统虚拟化模块。它使用Linux自身的调度器进行管理&#xff0c;所以相对于Xen&#xff0c;其核心源码很少。目前KVM已成为学术界的主流VMM之一&#xff0c;它包含一个为处理器提供底层虚拟化 可加载…

企业图纸混乱怎么办?

企业图纸混乱怎么办&#xff1f; 随着企业办公自动化的迅速发展&#xff0c;各种技术资料和电子文件日益庞大&#xff0c;图文档管理工作出现新的变化和考验。 在传统的管理模式下&#xff0c;企业的图纸文档分散在各个部门的个人电脑上&#xff0c;致使企业在进行图文档管理的…

Android开发:(AndroidStudio模拟器)如何将模拟器语言设置为中文 模拟器输入法更改为中文输入 键盘输入中文

文章目录 Android开发模拟器设置将模拟器语言设置为中文输入法中文的设置 Android开发模拟器设置 将模拟器语言设置为中文 第一步&#xff1a;打开模拟器后&#xff0c;上滑打开下面的设置图标。 第二步&#xff1a;找到 System (系统) &#xff0c;点击进入。 第三步&am…

nginx反向代理配置

1.1 安装nginx 本节以安装“nginx-1.7.9”为例讲解nginx的安装方法&#xff0c;请确认已获取了“nginx-1.7.9.tar.gz”包。 步骤 1 以root用户登录服务器。 步骤 2 通过SSH或XFTP等工具将nginx安装包“nginx-1.7.9.tar.gz”上传到Linux服务器的“/tmp”目录下。 步骤 3 进入…

MATLAB 模糊设计器 构建 模糊系统

系列文章目录 文章目录 系列文章目录前言一、创建 FIS 结构二、定义输入变量三、定义输出变量四、定义成员函数五、定义规则库六、设计分析七、存储和修改设计八、导出 FIS总结 前言 本例演示如何使用 Fuzzy Logic Designer 应用程序交互式创建 1 型 Mamdani 模糊推理系统&…

(免费)双相情感障碍筛查MDQ 在线测试双向情感障碍

MDQ用于筛查双相障碍&#xff0c;主要包含13个关于双相障碍症状的是非问题&#xff0c;当前测试采用的量表为2010年杨海晨博士翻译版。该量表为目前世界范围内最常用的双相障碍筛查量表&#xff0c;目前在精神科门诊最为常用的量表之一。 双向情感障碍筛查量表&#xff0c;也叫…

JVM虚拟机:垃圾回收器ZGC和Shenandoah算法

随着计算机技术的不断发展,内存管理成为了一个重要的话题。垃圾回收是一种自动内存管理技术,它可以自动地回收不再使用的内存,从而减少内存泄漏和程序崩溃的风险。在Java等高级编程语言中,垃圾回收器是必不可少的组件。近年来,ZGC和Shenandoah算法作为新一代的垃圾回收器,…

map和set的简易封装(纯代码)

RBTree.h #pragma once#include<iostream> #include<vector> using namespace std;enum colar { red,black };template<class T>//有效参数就一个 struct RBTreeNode {RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr)…

千兆光模块和万兆光模块需要注意哪些事项

随着网络通信技术的发展&#xff0c;千兆光模块和万兆光模块已经成为了网络设备中不可或缺的关键组件。光模块的制造涉及到许多技术和工艺问题&#xff0c;需要严格的控制和管理。本文将从工艺流程、材料选用、测试认证等方面&#xff0c;详细介绍制造千兆光模块和万兆光模块需…

储能领域 / 通讯协议 / 技术栈 等专有名字集锦——主要收集一些储能领域的专有名词,以及相关的名词

目录 名词解释ModbusIOT设备通讯协议 CAN/ RS-485 储能术语电池管理系统BMS电池储能系统相关概念&#xff0c;总控&#xff0c;主控&#xff0c;从控 电池相关知识拆解电池的构成逆变器 电池核心参数SOC 电池剩余容量 名词解释 英文中文biz layer业务层与业务层通信的服务CRC循…

【MySQL8】1130 - Host *** is not allowed to connect to this MySOL server

问题描述 使用 Navicat 连接 MySQL8 报错&#xff1a; 1130 - Host *** is not allowed to connect to this MySOL server解决方案 use mysql;select host ,user from user; -- 将 root 用户的主机&#xff08;host&#xff09;值修改为 %&#xff0c;即允许从任何主机连接 …

Kubernetes学习-概念2

参考&#xff1a;关于 cgroup v2 | Kubernetes 关于 cgroup v2 在 Linux 上&#xff0c;控制组约束分配给进程的资源。 kubelet 和底层容器运行时都需要对接 cgroup 来强制执行为 Pod 和容器管理资源&#xff0c; 这包括为容器化工作负载配置 CPU/内存请求和限制。 Linux 中…