当前位置: 首页 > news >正文

数据结构之红黑树

一、红黑树的概念

        红黑树是一棵二叉搜索树,他的每个结点增加一个存储位来表示结点的颜色,可以是红色,也可以是黑色。通过对任何一条从根到叶子的路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出两倍,因而接近平衡

1.红黑树的规则

        (1)每个结点不是红色就是黑色

        (2)根节点是黑色的

        (3)如果一个结点是红色的,那么它的两个孩子结点必须是黑色的,也就是说,任意一条路径不会有连续的红色结点。

        (4)对于任意一个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的黑色结点。

        说明:《算法导论》等书上补充了每个叶子结点(NIL)都是黑色的规则。他这里所指的叶子结点不是传统意义上的叶子结点,而是我们说的空结点,有些书籍上也把NIL叫做外部结点。NIL是为了方便准确的表示出所有路径,《算法导论》在后续讲解实现的细节中也忽略了NIL结点,所以我们了解即可

2.思考一下,红黑树如何确保最长路径不超过最短路径的2倍的?

        由规则(4)可知,从根到NULL结点的每条路径都有相同数量的黑色结点,所以在极端场景下,最短路径就是全是黑色的结点的路径,假设最短路径长度为bh(black height)

        由规则(2)和规则(3)可知,任意一条路径不会有连续的红色结点,所以极端场景下,,最长的路径就是由黑红间隔交替组成,那么最长路径的长度为2*bh。

        综合红黑树的4点规则而言,理论上的全黑最短路径和一黑一红的最长路径并不是在每颗红黑树都存在的。假设任意一条从根到NULL结点路径的长度为x,那么bh<=h<=2*bh。

3.红黑树的效率

        假设N是红黑树中结点数量,h最短路径的长度,那么2^h-1<=N<=2^2*h-1,由此推出h约等于logN,也就是意味着红黑树增删改查最坏也就是走最长路径2*logN,那么时间复杂度还是O(logN)。

        红黑树的表达相对于AVL树要抽象一些,AVL树通过高度差直观的控制了平衡。红黑树通过4条规则的颜色约束,间接的实现了近似平衡,它们的效率都是同一档次,但是相对而言,插入相同数量的结点,红黑树的旋转次数的更少的,因为他对平衡的控制没有那么严格。

二、红黑树的实现

        1.红黑树的结构

//枚举值表示颜色
enum Color {RED,BLACK
};
//按Key/Value结构实现
template<class K,class V>
struct BRTreeNode {pair<K, V> _kv;int _col;BRTreeNode<K,V>* _parent;BRTreeNode<K, V>* _left;BRTreeNode<K, V>* _right;BRTreeNode(const pair<K,V>& kv):_col(RED),_parent(nullptr),_left(nullptr),_right(nullptr),_kv(kv){ }
};

如上图,为红黑树结点的实现。

template<class K,class V>
class BRTree {using BRNode = BRTreeNode<K, V>;
public:BRTree():_root(nullptr){}
private:
//红黑树的根节点BRNode* _root;
};

如上图,为红黑树的定义

2.红黑树的插入

        2.1红黑树插入一个值的大概过程

        (1)插入一个值按二叉搜索树规则进行插入,插入后我们只需观察是否符合红黑树的4条规则。

        (2)如果是空树插入,新增结点就是黑色结点。如果是非空树,新增结点必须为红色结点,因为非空树插入,新增黑色结点就破坏了规则4,规则4是很难维护的。

        (3)非空树插入后,新增结点必须为红色结点,如果父亲结点是黑色的,则没有违反任何规则,插入结束。

        (4)非空树插入后,新增结点必须为红色结点,如果父亲结点是红色的,则违反规则3。进一步开始分析,c为红色,p为红色,g必为黑色,这三个颜色都固定了,关键看u的情况,需要根据u分为以下几种情况处理

        **注意:我们把新增结点称为c(cur)c的父亲结点为p(parent),p的父亲结点为g(grandparent) ,p的兄弟结点(uncle)

        2.2 情况1:变色

c为红,p为红,g为黑,u存在且为红,则将p和u变黑,g变红,将g当做新的c,继续向上更新。

分析:因为p和u都为红,g为黑,从g往下走,不是过p就是过u,将g变红,p和u变黑,即保证了g往下面走的黑色结点个数保持不变,同时将红结点向上更新,进一步处理。如果一直下去,更新到根节点,直接将其置为黑,结束。如果发现p为黑色,满足红色不与红色相连,也可结束。

情况1只变色,不旋转。无论c是p的左还是右,p是g的左还是右,都是上面的处理方式。

**跟AVL树类似,我们展示了一个具体情况,但实际情况会有很多。

**如图,将上述进行抽象表示,d/e/代表每条路径都拥有hb个黑色结点的子树,,a/b代表有hb-1个黑色结点,根为红的子树。hb>=0.

        2.3 情况2:单旋+变色

c为红,p为红,g为黑,u不存在或者u存在且为黑,u如果不存在,则c一定为新增结点

原因:如果c为下面更新上来的,则下面必有黑色结点,但g->u这条路径无黑色结点了,因此必然c为新增

u存在且为黑,则c一定不是新增结点

原因:如果c是新增结点由g->p->c这条路径上只有g是黑色结点,但由g->u这条路径有g u两个黑色结点

分析:p必须变黑才能解决连续红色结点的问题,u不存在或者为黑色,这里单纯的变色无法解决问题,需要旋转加变色。

          g

     p       u

c

        如果p是g的左,c是p的左,那么以g为旋转带你进行右单旋,再把p变黑,g变红即可。

       g

u                    

                       c

        如果p是g的右,c是p的右,那么以g为旋转点进行左单旋,再把p变黑,g变红即可。p变成这棵树的根,这样子树的黑色结点数量也不变,没有连续的红色结点了,且不需要往上更新,因为p的父亲是黑色还是红色都不违反规则。

2.4 情况3:双旋+变色

        这种情况不是上面那种“左边红的左边红”和“右边红的右边红”,需要进行双旋+变色

            g

       p         u

            c

        如果p是g的左,c是p的右,那么先以p为旋转点进行左单选,让其变成“左边红的左边红”,就转化为情况1了,再以g为旋转点进行右旋。

              g

        u          p

              c

        如果p是g的右,c是p的左,那么先以p为旋转点进行右单旋,让其变成“右边红的右边红”,就转化为情况1了,再以g为旋转点进行左旋。

3.红黑色的插入代码实现

3.1 红黑树结点的定义

enum Color {RED,BLACK
};
//红黑树结点
template<class K,class V>
struct BRTreeNode {pair<K, V> _kv;//数据用pair来实现int _col;//颜色BRTreeNode<K,V>* _parent;BRTreeNode<K, V>* _left;BRTreeNode<K, V>* _right;
//初始化构造BRTreeNode(const pair<K,V>& kv):_col(RED),_parent(nullptr),_left(nullptr),_right(nullptr),_kv(kv){ }
};

3.2 红黑树结构定义

template<class K,class V>
class BRTree {using BRNode = BRTreeNode<K, V>;
public:
private:BRNode* _root;
};

3.3 红黑树插入方法

bool insert(const pair<K, V>& kv) {//定义待会要插入的新结点BRNode* Node = new BRNode(kv);if (_root == nullptr) {//如果根为空,直接插入,退出_root = Node;Node->_col = BLACK;return true;}//记录g,p,uBRNode* parent = _root;BRNode* uncle = nullptr;BRNode* g_parent = nullptr;while (parent) {//寻找要插入的地方//大于结点 右边走if (Node->_kv > parent->_kv) {if(parent->_right)parent = parent->_right;//大于,继续向右查找else {//找到插入位置,插入,退出循环parent->_right = Node;Node->_parent = parent;break;}}else if(Node->_kv<parent->_kv){if(parent->_left)parent = parent->_left;//小于,继续向左查找else {//找到插入位置,插入,退出循环parent->_left = Node;Node->_parent = parent;break;}}else {return  false;}}while (parent) {//防止爷爷结点为空if (parent->_col == BLACK || parent == _root) {//如果更新到父节点为黑,或者父节点为根节点,退出break;}else {//找到爷爷结点,进而找出uncle结点来进行判断g_parent = parent->_parent;if (parent == g_parent->_left) {uncle = g_parent->_right;}else {uncle = g_parent->_left;}//父亲为红if (uncle == nullptr|| uncle->_col == BLACK) {//叔叔不存在||为黑//要旋转加变色// 1.左边红的左边红->右旋if (parent == g_parent->_left && parent->_left == Node) {RotateR(g_parent);}// 2.右边红的右边红->左旋else if (parent == g_parent->_right && parent->_right == Node) {RotateL(g_parent);}//3.左边红的右边红->左右双旋else if (parent == g_parent->_left && parent->_right == Node) {RotateLR(g_parent);}//4.右边红的左边红->右左双旋else if (parent == g_parent->_right && parent->_left == Node) {RotateRL(g_parent);}else {assert("error");}break;}else {//叔叔存在且为红//变色g_parent->_col = RED;uncle->_col = BLACK;parent->_col = BLACK;//继续向上更新Node = g_parent;parent = g_parent->_parent;}}}_root->_col = BLACK;return true;}

3.4 旋转方法

3.4.1 左单旋
	//左单旋void RotateL(BRNode* parent) {BRNode* SubR = parent->_right;BRNode* NodeL = SubR->_left;//爷爷辈和左子树根节点连if (parent == _root) {_root = SubR;SubR->_parent = nullptr;}else {BRNode* pparent = parent->_parent;if (pparent->_left == parent) {pparent->_left = SubR;SubR->_parent = pparent;}else {pparent->_right = SubR;SubR->_parent = pparent;}}//左子树和parent左链接parent->_right = SubR->_left;if (NodeL != nullptr)NodeL->_parent = parent;//parent和左子树的根节点连parent->_parent = SubR;SubR->_left = parent;//SubR要变成局部子树的根节点,要变黑,//p要变成红,从而保证从SubR下来的黑色结点数量相同SubR->_col = BLACK;parent->_col = RED;}
3.4.2 右单旋
	//右单旋void RotateR(BRNode* parent) {BRNode* SubL = parent->_left;BRNode* NodeR = SubL->_right;//爷爷辈和左子树根节点连if (parent == _root) {_root = SubL;SubL->_parent = nullptr;}else {BRNode* pparent = parent->_parent;if (pparent->_right == parent) {pparent->_right = SubL;SubL->_parent = pparent;}else {pparent->_left = SubL;SubL->_parent = pparent;}}//左子树和parent左链接parent->_left = SubL->_right;if (NodeR != nullptr)NodeR->_parent = parent;//parent和左子树的根节点连parent->_parent = SubL;SubL->_right = parent;SubL->_col = BLACK;parent->_col = RED;}
3.4.3 左右双旋&&右左双旋
//左右双旋
void RotateLR(BRNode* pParent) {BRNode* parent = pParent->_left;BRNode* SubR = parent->_right;RotateL(parent);RotateR(pParent);pParent->_col = RED;SubR->_col = BLACK;parent->_col = RED;
}
//右左双旋
void RotateRL(BRNode* pParent) {BRNode* parent = pParent->_right;BRNode* SubL = parent->_left;RotateR(parent);RotateL(pParent);pParent->_col = RED;SubL->_col = BLACK;parent->_col = RED;
}
4.红黑树的查找

按照二叉搜索树逻辑实现即可,搜索效率为O(logN)

	bool find(const K& k) {BRNode* parent = _root;while (parent) {if (parent->_kv.first > k) {parent = parent->_left;}else if(parent->_kv.first<k){parent = parent->_right;}else {return true;}}return false;}
5.红黑树的验证

        这里获取最长路径和最短路径,检查最长路径不超过最短路径的2倍是不可行的,因为就算满足这个条件,红黑树颜色可能也不满足规则,当前暂时吗出问题,后续可能还是会出问题,所以我们去检查4点规则,满足4点规则,一定能保证最长路径不超过最短路径的二倍。

1.规则(1)枚举颜色类型,天然保证了不是红就是黑

2.规则(2)直接检查根

3.规则(3)前序遍历检查(因为4会统计每条路径的黑色个数,因此用前序遍历),遇见红色结点查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲的颜色就方便多了

4.规则(4)前序遍历,遍历过程中用形参记录根到当前结点的blackNum(黑色结点数量),前序遍历遇见黑色结点,就++blackNum,走到空就计算出了一条路径的黑色结点数量。再取任意一条路径的黑色结点总数作为参考值,依次比较即可。

bool Check(BRNode* root,int BN,int RefNum) {//BN为黑色的数量if (root == nullptr) {if (BN != RefNum) {cout << "各个路径的黑色结点不相等" << endl;return false;}else {return true;}}if (root&& root->_parent) {if (root->_parent->_col == RED && root->_col == RED) {cout << "存在连续两个红色节点" << endl;return false;}}if (root->_col == BLACK) {BN++;}return Check(root->_left, BN, RefNum) && Check(root->_right, BN, RefNum);
}
http://www.dtcms.com/a/594116.html

相关文章:

  • 上海 网站备案拍照推广公众号平台的公司
  • 自学网站有哪些深圳建企业网站
  • 课程网站开发合同英文网站有哪些
  • Android内核进阶之pcm硬件参数最小约束值snd_pcm_hw_param_first:用法实例(八十七)
  • Node-RED:输入节点全家桶:数据从哪里来?
  • AI 大模型训练 / 推理的 CPU/GPU 选型指南整理 (仅供参考)
  • 桂林网站优化公司wordpress换空间搬家
  • 青岛网站建设制作公司WordPress 网站成本
  • 现代数据库系统数据结构 B+Tree
  • 佛山专业网站营销企业官方网站管理制度
  • 竞价单页网站制作教程阿里巴巴国际站怎么找客户
  • Attention复杂度解析与改进方向
  • 化工网站建设推广南通做网站的
  • 寻找网站建设员网站开发要跑道吗
  • 集成式智能体开发流程提示词
  • 保定免费建站服务医院男性男科
  • 农业数据集目标检测分割分类数据集汇总介绍
  • 做网站公司在深圳培训学校机构有哪些
  • 织梦 两个网站网站该怎么找到
  • vscode实现ssh远程连接
  • 网站设计过时九江市建设规划局网站
  • 外贸网站平台哪个好ico wordpress
  • 做的网站不能放视频播放器html简单网页代码作业
  • 鄠邑区建设和住房保障局网站永安城乡建设局网站
  • Flutlab使用详解
  • Spring 配置解析与 @Value 注入核心流程详解
  • 亳州网站开发wordpress文章阅读数更改
  • 数据结构**优先级队列**超详细入门到进阶宝典
  • 新药研发项目管理的困境与挑战,医药项目管理系统助推新药研发水平提升
  • 网站首页生成静态页面logo公司商标设计