C++进阶必备:红黑树从 0 到 1: 手撕底层,带你搞懂平衡二叉树的平衡逻辑与黑高检验

这一篇是红黑树。

目录

1.红黑树的概念(定义,规则,效率)

1.1红黑树的规则

1.2红黑树的效率

2.红黑树的底层实现

2.1红黑树的基本框架

2.2红黑树的插入

情况1:变色:条件:u存在且为红

​编辑

情况2:单旋+变色:条件:u不存在或存在且为黑

情况3:双旋+变色:条件:情况2的基础上,不是单纯的一边高

插入的完成代码展示:

2.3红黑树的查找

2.4红黑树的常用接口:size,height,InOrder

2.5红黑树的平衡验

1.红黑树的概念(定义,规则,效率)

红黑树是一颗平衡二叉搜索树,他的每个结点增加一个存储位来表示结点的颜色,红色,或黑色。通过约束任意一条从根到叶子的路径的结点颜色,红黑树确保了没有一条路径比其他路径长两倍,因此它的平衡的。

1.1红黑树的规则

1.每个结点不是红色,就是黑色。

2.根节点是黑色的。

3.如果一个结点是红色的,那与它直接相连的必须为黑色。也就是说:一条路径中不会出现连续的红色结点。

4.黑色结点可以连续存在

5.从任意一个节点(或同高度的节点,根节点)到其后代 NULL 叶子节点的所有路径上,包含的黑色节点数量完全相等

6.最短路径bh(blackheight,黑高。全黑,不用黑红交替,就是最短,所以是黑高),最长路径不会超过(可以等于)2bh(全部红黑交替)(注意,算黑高,算路径,必须包含NULL结点,算普通的层数高度,不需要算NULL结点)易推导:任意一条路径高h(包含NULL),bh<=h<=2bh

7.红黑树中所有路径的统计必须以黑色的 NULL 叶子结点为终点,它必须是黑色的。

7.图示

4.图示

1.2红黑树的效率

假设N为红黑树中结点数,h为最短路径,可推出: 。由此: ,那红黑树增删查,最差也就是 ,那它的时间复杂度就是

红黑树相比AVL树更抽象,AVL树通过控制高度差,直观地控制了平衡。红黑树通过多条规则约束,间接的实现了平衡。他们的效率都是同一档次,log N,但是相对的,插入结点数相同,红黑树旋转次数更少,因为它可以避免不必要的旋转(单纯修改颜色就行,实现底层具体说明)

2.红黑树的底层实现

2.1红黑树的基本框架

这里用枚举来定义颜色,在类中加入一个新的变量,用来存储结点颜色,其余类似。

复制代码
//枚举表示颜色
enum Colour
{
    BLACK,
    RED

};

// 这里我们默认按key/value结构实现
template<class K, class V>
struct RBTreeNode
{
    // 这里更新控制平衡也要加入parent指针
    pair<K, V> _kv;
    RBTreeNode<K, V>* _left;
    RBTreeNode<K, V>* _right;
    RBTreeNode<K, V>* _parent;
    Colour _col;

    RBTreeNode(const pair<K, V>& kv)
        :_kv(kv)
        , _left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
    {}
};

template<class K, class V>
class RBTree
{
    typedef RBTreeNode<K, V> Node;
public:
private:
    Node* _root = nullptr;
};

2.2红黑树的插入

红黑树插入一个值的过程:

1.因为底层是二叉搜索树,所以插入过程与二叉搜索树一致。插入后观察是否符合红黑树规则

2.如果是空树插入,新增结点是黑色结点。若是非空树插入,新增结点必须是红色结点。若新增黑色结点,会破坏规则,并且黑色结点很难处理(黑高失衡,增加一个黑色,会影响经过这结点的所有路径的黑高,牵一发而动全身,一个个去修复,很难。)。

3.非空树,新增结点必须是红色结点,若父亲是黑色结点,则没违反规则,插入结束。

4.非空树,新增结点必须是红色结点,若父亲是红色结点 ,因为有连续红色结点 ,所以破坏了规则 ,进一步分析:c是红色,p是红色,那g必为黑(插入此节点之前,肯定是满足之前规则的,所以g和p不能构成连续红色),这三个颜色都固定了,此时只需要关注u的颜色变化:

情况1:变色:条件:u存在且为红

情况1:变色:条件:u存在且为红只需要将p,u变黑色,g变红色(g往下的路径,黑色结点数量还是不变,子树整体不受到影响,外层视作无变化,内部结构变了)。若g的p为黑色,无连续红色,结束更新。反之继续往上更新(c变成g,对应关系都变化)。若更新到了根,则不需要向上更新(g->p为空),把(g)根节点改为黑色。

无论c是p的左右,p是g的左右,变色处理方式一致。因为本质是处理连续的红色结点和黑色结点数量不平衡问题,不需要旋转,无关左右。

下图是抽象图,子树内部情况有多种,bh为2时,情况已组合到上百亿级别,所以既然处理方式一样,那就不徒增麻烦了。

情况2:单旋+变色:条件:u不存在或存在且为黑

u不存在,则c一定是新增结点(如果u不存在,那c存在的话,c之前肯定是符合要求的,导致c是黑色结点,但是如果c是黑色,左子树多了一个黑色结点数量,导致黑高失衡,所以c肯定是新增结点)

u存在且为黑,c一定不是新增结点(如果已存在,c必然是黑色结点,但却是红色,所以:c一定是之前符合情况1,向上更新的途中,由g变为c的,变色过后的。)

分析:p必须变黑,才能解决连续红色结点。u不存在,或存在且为黑,也就是无法变黑,那必然导致黑高失衡。此时单纯变色(怎么变色,都是少一个黑高)无法解决问题,需要旋转+变色:

旋转又分左旋,右旋:(左右镜像,只讲一种)

对g结点进行右旋或左旋,"压下去"一层高度,再把p变黑,g变红。

当u存在且为红,c是黑色变来的。之前分析过,该子树黑高是不变的,c的黑色转移到抽象的子树中了(pu变黑,g变红,通过g的黑高没变化),所以别看具体是右边黑结点多一个,其实左边的转移到抽象子树中了。

下图证明一下:

情况3:双旋+变色:条件:情况2的基础上,不是单纯的一边高

一图知晓:

先对p左旋,再对g右旋。第一个左旋的目的是恢复成情况2。

插入的完成代码展示:

2.3红黑树的查找

与二叉搜索树完全一致。 效率O(log N)

2.4红黑树的常用接口:size,height,InOrder

递归实现,十分简单。

2.5红黑树的平衡验证

可不可以通过获取**:最短路径bh与最长路径2bh验证平衡** ?答案是不行。因为满足这个不一定是红黑树,但是红黑树一定满足。所以要利用红黑树的根本逻辑(颜色)

1**.前序遍历,递归检查每个节点左右子树的黑高。**

2.还可以检查是否有连续红色:root为红,root父存在且为红,直接返回false(查找孩子是否是红色结点很难,因为可能不存在,但是查找父亲就容易,所以查找父亲来判断)

3.当前root是黑结点,定义变量记录黑结点++root_cur_bn

4.根据:从根到任意叶子结点的路径上,黑色结点数量必须相同记录最左路径,作为基准,传参,当前序遍历,走完一条路径时,当前路径黑结点也统计完,比较一下基准,看看是否相同,不相同返回false,不满足规则。

为啥递归到空,就可以检查基准,并且确保了每条路径都检查到了呢?

因为第一次递归肯定是最左路径,然后又会递归最左的结点的右结点,此时是新路径,这两条递归会去最左结点的父亲结点p,然后p结点递归右,那就是每次递归到空,都是遍历了一个路径,所以可以在为空时判断,并且判断的很全面,会覆盖每条路径。

当然这样性能损耗也不小,要遍历全部,时间复杂度O(N).

前中后序递归,都会递归遍历全部路径,只是顺序不同。

前序适合这个检查校验,因为前序是先统计,校验后,再逐次往下检查。

但是中序和后序,都需要"折返",先折腾,然后回来统计,所以不适合这里的校验。

内容结束

相关推荐
汉克老师3 小时前
GESP2025年6月认证C++二级( 第一部分选择题(1-8))
c++·循环结构·表达式·分支结构·gesp二级·gesp2级
熬夜有啥好3 小时前
数据结构——排序与查找
数据结构
YuTaoShao3 小时前
【LeetCode 每日一题】3634. 使数组平衡的最少移除数目——(解法二)排序 + 二分查找
数据结构·算法·leetcode
wangluoqi3 小时前
26.2.6练习总结
数据结构·算法
Risehuxyc3 小时前
备份三个PHP程序
android·开发语言·php
rainbow68893 小时前
C++高性能框架Drogon:后端开发新标杆
c++
Yvonne爱编码3 小时前
链表高频 6 题精讲 | 从入门到熟练掌握链表操作
java·数据结构·链表
Q741_1473 小时前
C++ 优先级队列 大小堆 模拟 力扣 703. 数据流中的第 K 大元素 每日一题
c++·算法·leetcode·优先级队列·
lly2024063 小时前
PHP Error: 常见错误及其解决方法
开发语言