从零开始的C++学习生活 13:红黑树全面解析

个人主页:Yupureki-CSDN博客

C++专栏:C++_Yupureki的博客-CSDN博客

目录

前言

[1. 红黑树的概念](#1. 红黑树的概念)

[1.1 红黑树的规则](#1.1 红黑树的规则)

[1.2 红黑树如何保证平衡?](#1.2 红黑树如何保证平衡?)

[2. 红黑树的实现](#2. 红黑树的实现)

[3.1 基本结构](#3.1 基本结构)

[3.2 插入操作详解](#3.2 插入操作详解)

[3.2.1 变色](#3.2.1 变色)

情况1:叔叔节点存在且为红色 (变色)

情况2:叔叔节点不存在或为黑色(旋转+变色)

单旋情况:

双旋情况:

[3.3 查找操作](#3.3 查找操作)

[3.4 红黑树的验证](#3.4 红黑树的验证)


上一篇:从零开始的C++学习生活 12:AVL树全面解析-CSDN博客

前言

前面我们学习了AVL树,一种高级的二叉搜索树,通过平衡因子控制树的高低差不超过2来保持树的平衡,可以大幅增加查找数据的效率

而今天,我们又有一种由AVL树而来的变种:红黑树。同样是高级的二叉搜索树,红黑树同样限制了树的最大高度来增加效率,但是方法有所差异

我将深入探讨红黑树的原理、特性及实现细节,帮助你全面理解这一重要数据结构的工作原理。

1. 红黑树的概念

红黑树是一种特殊的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色(红色或黑色)。通过对从根到叶子的任何路径上的节点颜色施加约束,红黑树确保没有任何一条路径会比其他路径长出两倍,因而保持了近似平衡的状态。

1.1 红黑树的规则

  1. 颜色规则:每个节点不是红色就是黑色

  2. 根节点规则:根节点必须是黑色的

  3. 红色节点规则 :红色节点的两个子节点必须是黑色的(即不能有连续的红色节点

  4. 黑色高度规则 :从任意节点到其所有NULL 节点的简单路径上,包含相同数量的黑色节点

总地来说,红黑树顾名思义,只有红和黑,没有蓝白树或者其他的。

当然这里红和黑只是一种标志,你要想变成其他的颜色其实也可以awa

回答我们所说的,通过上面的规则分析,可以知道不能由连续的红色节点,即父亲和孩子不能同时为红色,但兄弟就不用,毕竟都不是连续的。但是可以存在连续的黑色节点

要注意的是,所有路径上的黑色节点数量相同,这里的路径指的是从根节点到任意一个NULL节点,不是直观的只算有节点的路径。

这样能够保证红黑树的最小长度是全黑(N),最大长度是红和黑交叉出现(2*N),因此限制了红黑树的高度

1.2 红黑树如何保证平衡?

红黑树通过上述四条规则巧妙地维持了树的平衡:

  • 根据规则4,从根到NULL节点的每条路径都有相同数量的黑色节点。设最短路径(全黑路径)的黑色节点数为bh

  • 根据规则2和3,最长路径由黑红节点交替组成,其长度不超过2×bh

  • 因此,最长路径不会超过最短路径的两倍

假设红黑树有N个节点,最短路径长度为h,则有:

2^h - 1 < N < 2^(2×h) - 1

由此可得 h ≈ logN,这意味着红黑树的插入、删除和查找操作的最坏情况时间复杂度都是O(logN)。

与AVL树相比,红黑树对平衡的控制相对宽松,这使得在插入相同数量节点时,红黑树所需的旋转操作更少,整体性能更加稳定。

2. 红黑树的实现

3.1 基本结构

红黑树有红色和颜色,因此我们利用枚举常量

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

红黑树的节点和AVL树的节点相似,只不过多了颜色

cpp 复制代码
// 红黑树节点
template<class K, class V>
struct RBTreeNode {
    std::pair<K, V> _kv;
    RBTreeNode<K, V>* _left;
    RBTreeNode<K, V>* _right;
    RBTreeNode<K, V>* _parent;
    Colour _col;

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

红黑树本体也和AVL树极其相似

cpp 复制代码
template<class K, class V>
class RBTree {
    typedef RBTreeNode<K, V> Node;
private:
    Node* _root = nullptr;
    // 其他成员函数...
};

3.2 插入操作详解

红黑树的插入我们默认按照二叉搜索树的规则

如果是非空树 ,插入的一定是红色节点,因为红黑树保证每条路径上的黑色节点相同。如果插入黑色节点,那么会破坏相等这一条件

如果是空树,插入的节点作为根节点,颜色设为黑色

之后要检查并修复红黑树性质:如果父节点是黑色,插入完成;如果父节点是红色,需要根据叔叔节点的颜色进行不同的处理

3.2.1 变色

假设我们插入的节点之前是红色节点,那么就会出现红红的情况,因此我们需要进行变色处理

这里我们把新插入的节点37作为孩子,40作为父亲,45作为祖父,48作为叔叔

情况1:叔叔节点存在且为红色 (变色)

在这里叔叔存在且为红色,那么我们就把parent和uncle变为黑色,grandparent变为红色

因为grandparent为红色之后,可能grandparent的parent也为红色,例如下图,因此我们还需向上检查,把child赋值为grandparent

情况2:叔叔节点不存在或为黑色(旋转+变色)
单旋情况:

对于上面的右图,叔叔虽然存在但是为黑色,那么我们还需要进行旋转处理

这里我们专门把child,parent,grandparent拆分出来(uncle无需关心,同时把偷偷把child变到左边去,child在右边是双旋情况)

我们对grandparent进行右单旋,同时把parent变为黑色,grandparent变为黑色

到了这种情况,uncle要么不存在要么为黑色,我们不用管uncle到底存不存在,也不用进行任何操作,继续保持grandparent指向uncle即可,别问,问就是巧妙awa,可以细品

我们再把child赋值为parent,由于parent已经为黑色了,不管parent和parent是红色还是黑色,都不会冲突,因此可以直接退出

双旋情况:

对于child在parent的右边情况,和AVL树类似,我们需要先对parent进行左单旋,保证三个节点在一条直线上,才能对grandparent进行右单旋

插入过程完成代码:

cpp 复制代码
bool Insert(const K& key, const V& value)
{
	if (_root == nullptr)
	{
		_root = new Node({key,value});
		_root->_col = BLACK;
		return true;
	}
	Node* newnode = new Node({ key,value });
	newnode->_col = RED;
	Node* cur = _root;
	Node* child = cur;
	while (cur)//按照二叉搜索树的规则插入
	{
		if (key < cur->_kv.first)
		{
			child = cur;
			cur = cur->_left;
		}
		else if (key > cur->_kv.first)
		{
			child = cur;
			cur = cur->_right;
		}
		else
		{
			find(key)->_kv.second++;
			return true;
		}
	}
	if (key < child->_kv.first)
	{
		child->_left = newnode;
		newnode->_parent = child;
	}
	else
	{
		child->_right = newnode;
		newnode->_parent = child;
	}
	child = newnode;
	Node* parent = child->_parent;
	while (child->_col == RED && parent && parent->_col == RED)
//当child为红色时并且parent为黑色时继续循环
	{
		parent = child->_parent;
		Node* pparent = parent->_parent;
		Node* uncle = nullptr;
		if (pparent == nullptr)
			break;
		else
		{
			if (pparent->_left == parent)
				uncle = pparent->_right;
			else
				uncle = pparent->_left;

			if (uncle && uncle->_col == RED)//叔叔存在并且为红色直接变色
			{
				uncle->_col = BLACK;
				parent->_col = BLACK;
				pparent->_col = RED;
				child = pparent;
				parent = child->_parent;
			}
			else//叔叔不存在或者为黑色,需要旋转和变色
			{
				if (pparent->_left == parent)
				{
					if (parent->_right == child)
					{
						RotateL(parent);
						RotateR(pparent);
						child->_col = BLACK;
						pparent->_col = RED;
						
					}
					else
					{
						RotateR(pparent);
						parent->_col = BLACK;
						pparent->_col = RED;
						child = parent;
						parent = child->_parent;
					}
				}
				else
				{
					if (parent->_left == child)
					{
						RotateR(parent);
						RotateL(pparent);
						child->_col = BLACK;
						pparent->_col = RED;
					}
					else
					{
						RotateL(pparent);
						parent->_col = BLACK;
						pparent->_col = RED;
						child = parent;
						parent = child->_parent;
					}
				}
			}
		}
	}
	_root->_col = BLACK;//在变色处理时可能会对根节点进行变动,根节点需要保持黑色

	return true;
}

3.3 查找操作

红黑树的查找操作与普通二叉搜索树完全相同,时间复杂度为O(logN)。

cpp 复制代码
Node* Find(const K& key) {
    Node* cur = _root;
    while (cur) {
        if (cur->_kv.first < key) {
            cur = cur->_right;
        } else if (cur->_kv.first > key) {
            cur = cur->_left;
        } else {
            return cur;
        }
    }
    return nullptr;
}

3.4 红黑树的验证

验证红黑树是否满足所有规则是确保实现正确的关键。验证方法包括:

  1. 检查根节点是否为黑色

  2. 检查是否存在连续的红色节点

  3. 检查所有路径的黑色节点数量是否相同

我们利用递归来实现

规则1很好判断

规则2我们对每个节点和其父节点检查即可

规则3我们可以定义一个专门统计一条路径上黑色节点数量的变量,一直传给下一个节点,随后判断左右两条路是否相等即可

cpp 复制代码
bool Check(Node* root, int blackNum, const int refNum) {
    if (root == nullptr) {
        // 到达叶子节点,检查黑色节点数量
        if (refNum != blackNum) {
            cout << "存在黑色结点的数量不相等的路径" << endl;
            return false;
        }
        return true;
    }
    
    // 检查是否存在连续的红色节点
    if (root->_col == RED && root->_parent->_col == RED) {
        cout << root->_kv.first << "存在连续的红色结点" << endl;
        return false;
    }
    
    if (root->_col == BLACK) {
        blackNum++;
    }
    
    return Check(root->_left, blackNum, refNum) && 
           Check(root->_right, blackNum, refNum);
}
相关推荐
2401_876221345 小时前
Euler
c++·数学·算法
Z...........5 小时前
优选算法(滑动窗口)
数据结构·算法
大数据张老师5 小时前
数据结构——平衡二叉树(2)
数据结构
AhriProGramming5 小时前
Python学习快速上手文章推荐(持续更新)
开发语言·python·学习·1024程序员节
泡泡鱼(敲代码中)6 小时前
数据结构(顺序表和链表)
笔记·学习·算法
无妄无望6 小时前
在没有网络的环境下安装包pymysql
学习·docker
酌量6 小时前
基于3D激光点云的障碍物检测与跟踪---(3)基于匈牙利算法的障碍物跟踪
学习·算法·机器人·匈牙利算法·障碍物跟踪
赵杰伦cpp6 小时前
C++的继承机制精讲
java·开发语言·c++·后端
tongsound7 小时前
记录一次崩溃问题排查过程(gtsam库相关,avx)
linux·c++