手撕红黑树

news2024/11/30 2:38:28

目录

一、概念

二、红黑树的插入操作

第一步: 按照二叉搜索树的规则插入新节点

第二步: 插入后检测性质是否造到破坏,若遭到破坏则进行调整

情况一: cur为红,parent为红,grandfather为黑,uncle存在且为红

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑(单旋+变色)

情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑(双旋+变色)

三、红黑树的验证

检测其是否满足二叉搜索树

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

四、完整代码

五、红黑树与AVL树的比较


一、概念

红黑树,是一种二叉搜索树。但在每个结点上增加一个存储位表示结点的颜色,可以是Red或
Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
径会比其他路径长出两倍,因而是接近平衡的。

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

二、红黑树的插入操作

红黑树的插入操作大致可以分成两步:

第一步: 按照二叉搜索树的规则插入新节点

bool insert(const pair<K, V>& kv) {
		if (_root == nullptr) {
			_root = new TreeNode(kv);
			_root->_color = BLACK;
			return true;
		}

		TreeNode* parent = nullptr;
		TreeNode* cur = _root;
		while (cur != nullptr) {
			if (kv.first > cur->_data.first) {
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_data.first) {
				parent = cur;
				cur = cur->_left;
			}
			else return false;
		}
		cur = new TreeNode(kv);
		cur->_color = RED;
		if (kv.first > parent->_data.first) {
			parent->_right = cur;
		}
		else { //kv.first < parent->_data.first)
			parent->_left = cur;
		}
		cur->_parent = parent;

		//………………
	}

第二步: 插入后检测性质是否造到破坏,若遭到破坏则进行调整

新节点的默认颜色是红色,若其双亲结点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入结点的双亲结点颜色为红色时,就出现了连续的红色结点,此时需要对红黑树分情况来讨论:

情况一: cur为红,parent为红,grandfather为黑,uncle存在且为红

if (uncle != nullptr && uncle->_color == RED) {
    parent->_color = uncle->_color = BLACK;
	grandfather->_color = RED;
	cur = grandfather;
	parent = cur->_parent;
}

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑(单旋+变色)

uncle的情况有两种:

1.若uncle结点不存在时,cur结点一定是新增结点。若cur不是新增结点,则cur和parent之间一定有一个黑色结点。这不满足性质4:每条路径上黑色结点的个数相同。

2.若uncle存在且为黑色,那么cur原来的颜色一定为黑色。看到cur结点是红色,是因为cur的子树在调整的过程中将cur的颜色从黑色改变为红色。

//右单旋 + 变色
if (cur == parent->_left) {
	rotate_right(grandfather);
	grandfather->_color = RED;
	parent->_color = BLACK;
}

//左单旋 + 变色
if (cur == parent->_right) {
	rotate_left(grandfather);
	grandfather->_color = RED;
	parent->_color = BLACK;
}

情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑(双旋+变色)

//左右双旋 + 变色
else {//cur == parent->_right
	rotate_left(parent);
    rotate_right(grandfather);
	cur->_color = BLACK;
	grandfather->_color = RED;
}

//右左双旋 + 变色
else {//cur == parent->_left
	rotate_right(parent);
	rotate_left(grandfather);
	cur->_color = BLACK;
	grandfather->_color = RED;
}

三、红黑树的验证

红黑树的检测分为两步:

检测其是否满足二叉搜索树

使用中序遍历判断其是否有序即可,这里不做过多解释

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

bool IsBalance() {
	//空树也是红黑树
	if (_root == nullptr) return true;

	//根结点是黑色的
	if (_root->_color != BLACK) return false;

	int benchmark = 0;//基准值
	return _IsBalance(_root, 0, benchmark);
}

bool _IsBalance(TreeNode* root, int blackNum, int& benchmark) {
	if (root == nullptr) {
		if (benchmark == 0) {
			benchmark = blackNum;//将第一条路径的blackNum设为基准值
	    	return true;
		}
		else {
			return blackNum == benchmark;
		}
	}
	if (root->_color == BLACK) ++blackNum;

	if (root->_color == RED && root->_parent->_color == RED) return false;
    //逻辑短路,若root结点为红色,其就不可能为根结点,一定有parent结点

	return _IsBalance(root->_left, blackNum, benchmark) && 
            _IsBalance(root->_right, blackNum, benchmark);
}

四、完整代码

#include<iostream>
#include<cassert>
using std::pair;
using std::make_pair;
using std::cout;
using std::cout;
using std::endl;

enum Color { RED,BLACK };
template<class K,class V>
struct RedBlackTreeNode {
	RedBlackTreeNode(const pair<K, V>& kv) :
        _parent(nullptr), 
        _left(nullptr), 
        _right(nullptr), 
        _data(kv),
        _color(RED){}

	RedBlackTreeNode<K, V>* _parent;
	RedBlackTreeNode<K, V>* _left;
	RedBlackTreeNode<K, V>* _right;
	pair<K, V> _data;
	Color _color;
};

template<class K,class V>
class RedBlackTree 
{
	typedef RedBlackTreeNode<K, V> TreeNode;
public:
	bool insert(const pair<K, V>& kv) {
		if (_root == nullptr) {
			_root = new TreeNode(kv);
			_root->_color = BLACK;
			return true;
		}

		TreeNode* parent = nullptr;
		TreeNode* cur = _root;
		while (cur != nullptr) {
			if (kv.first > cur->_data.first) {
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_data.first) {
				parent = cur;
				cur = cur->_left;
			}
			else return false;
		}
		cur = new TreeNode(kv);
		cur->_color = RED;
		if (kv.first > parent->_data.first) {
			parent->_right = cur;
		}
		else { //kv.first < parent->_data.first)
			parent->_left = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_color == RED)
		{
			TreeNode* grandfather = parent->_parent;
			assert(grandfather != nullptr);
            //当parent结点为红时,grandfather结点必不为空(根结点为黑)
			assert(grandfather->_color == BLACK);
            //当parent结点为红时,grandfather结点必为黑色(否则违反性质,出现连续的红色结点)

			if (parent == grandfather->_left) {
				TreeNode* uncle = grandfather->_right;
				if (uncle != nullptr && uncle->_color == RED) {
					parent->_color = uncle->_color = BLACK;
					grandfather->_color = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				else {//uncle不存在或者为黑
					//右单旋 + 变色
					if (cur == parent->_left) {
						rotate_right(grandfather);
						grandfather->_color = RED;
						parent->_color = BLACK;
					}
					//左右双旋 + 变色
					else {//cur == parent->_right
						rotate_left(parent);
						rotate_right(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break;
				}
			}
			else {//parent == grandfather->_right
				TreeNode* uncle = grandfather->_left;
				if (uncle != nullptr && uncle->_color == RED) {
					parent->_color = uncle->_color = BLACK;
					grandfather->_color = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				else {//uncle不存在或者为黑
					//左单旋 + 变色
					if (cur == parent->_right) {
						rotate_left(grandfather);
						grandfather->_color = RED;
						parent->_color = BLACK;
					}
					//右左双旋 + 变色
					else {//cur == parent->_left
						rotate_right(parent);
						rotate_left(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break;
				}
			}
		}
		_root->_color = BLACK;
		return true;
	}

	void inorder() {
		_inorder(_root);
	}

	bool IsBalance() {
		//空树也是红黑树
		if (_root == nullptr) return true;

		//根结点是黑色的
		if (_root->_color != BLACK) return false;

		int benchmark = 0;//基准值
		return _IsBalance(_root, 0, benchmark);
	}
private:
	void _inorder(TreeNode* root) {
		if (root == nullptr) {
			return;
		}
		_inorder(root->_left);
		cout << root->_data.first << ":" << root->_data.second << " ";
		_inorder(root->_right);
	}

	bool _IsBalance(TreeNode* root, int blackNum, int& benchmark) {
		if (root == nullptr) {
			if (benchmark == 0) {
				benchmark = blackNum;
				return true;
			}
			else {
				return blackNum == benchmark;
			}
		}
		if (root->_color == BLACK) ++blackNum;

		if (root->_color == RED && root->_parent->_color == RED) return false;
        //逻辑短路,若root结点为红色,其就不可能为根结点,一定有parent结点

		return _IsBalance(root->_left, blackNum, benchmark) && 
                _IsBalance(root->_right, blackNum, benchmark);
	}

	void rotate_left(TreeNode* parent) {
		TreeNode* subR = parent->_right;
		TreeNode* subRL = subR->_left;
		TreeNode* pparent = parent->_parent;

		parent->_right = subRL;
		if (subRL != nullptr) subRL->_parent = parent;
		subR->_left = parent;
		parent->_parent = subR;

		//解决根结点变换带来的问题
		if (_root == parent) {
			_root = subR;
			subR->_parent = nullptr;
		}
		else {
			if (pparent->_left == parent) pparent->_left = subR;
			else pparent->_right = subR;
			subR->_parent = pparent;
		}
	}
	void rotate_right(TreeNode* parent) {
		TreeNode* subL = parent->_left;
		TreeNode* subLR = subL->_right;
		TreeNode* pparent = parent->_parent;

		parent->_left = subLR;
		if (subLR != nullptr) subLR->_parent = parent;
		subL->_right = parent;
		parent->_parent = subL;

		if (_root == parent) {
			_root = subL;
			subL->_parent = nullptr;
		}
		else {
			if (pparent->_left == parent) pparent->_left = subL;
			else pparent->_right = subL;
			subL->_parent = pparent;
		}
	}

private:
	TreeNode* _root = nullptr;
};

void RBTreeTest() {
	size_t N = 10000;
	srand((unsigned)time(NULL));
	RedBlackTree<int, int> t;
	for (size_t i = 0; i < N; ++i) {
		int x = rand();
		//cout << "insert:" << x << ":" << i << endl;
		t.insert(make_pair(x, i));
	}
	t.inorder();
	cout << t.IsBalance() << endl;

}
int main() 
{
	RBTreeTest();
	return 0;
}

五、红黑树与AVL树的比较

AVL树的平衡 (左右高度差不超过1) 相比,红黑树的平衡(没有一条路径会比其他路径长出两倍)并没有那么严格。所以两者在插入或删除相同数据时,红黑树需要旋转调整的次数更少,这使得红黑树的性能略高于AVL树。
可是AVL树更加平衡,查找数据所需的次数不是更加少吗?在AVL树与红黑树中进行数据的查找都十分快捷(譬如在查找100万数据中进行查找只需大概20次),对于CPU从时间上来说并不会造成什么负担。
总的来说,AVL树更适用于插入删除不频繁,只对查找要求较高的场景; 红黑树相较于AVL树更适应对插入、删除、查找要求都较高的场景,红黑树在实际中运用更加广泛。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JLink 添加新设备用于下载/调试固件

新驱动的安装目录结构如下&#xff1a; 可以看出新版本的 JLink 驱动中已经没有 Devices 目录和 JLinkDevices.xml 文件了&#xff0c;即旧的方法已经不能在新的驱动中使用了。 如果需要继续使用旧的方式添加新设备&#xff0c;则需要下载 JLink_V770d 之前的版本。 在新驱动…

若依框架解读(微服务版)—— 4.认证,登出(Gateway网关)

认证 我们可以查看token值 我们进入授权中心&#xff0c;这里其他的解析解析token的步骤与上一篇文章中的生成token是逆操作&#xff0c;也比较简单。我们进入ignoreWhite.getWhites()方法 此处的两个注解是获取nacos当中的白名单&#xff0c;我们打开nacos&#xff0c;进入网关…

Shell程序退出状态码的命令详解

在本篇文章当中主要给大家介绍了一些常见的程序退出的状态码&#xff01;并且给出一下例子帮助大家仔细理解&#xff0c;并且使用C语言和python语言实现获取子进程退出时候的退出状态码。 程序退出状态码 前言 在本篇文章当中主要给大家介绍一个shell的小知识——状态码。这是…

Object.defineProperty用法

Object.defineProperty() 定义新属性和修改原有的属性 Object.defineProperty( obj&#xff0c;prop,descriptor) 翻译&#xff1a;对象.定义属性&#xff08;对象&#xff0c;属性名必须是字符写法&#xff0c;{ value:所有 }&#xff09; 创建一个对象&#xff1a; var o…

OSI七层参考模型和TCP/IP四层(五层)参考模型

OSI七层参考模型 OSI&#xff08;OSI&#xff0c;Open System Interconnection&#xff09;七层模型&#xff0c;是参考模型是国际标准化组织&#xff08;ISO&#xff09;制定的一个用于计算机或通信系统间互联的标准体系。它是一个七层的、抽象的模型体&#xff0c;不仅…

泰勒展开式

泰勒展开式 文章目录泰勒展开式简介定义近似举例推导理解参考简介 泰勒公式&#xff0c;也称泰勒展开式&#xff0c;可以用来在局部范围内近似复杂函数。 通俗的讲&#xff1a; 设有一个复杂的未知函数f(x)f(x)f(x)&#xff0c;我们想要知道它在某个范围[a,b][a,b][a,b]内的值…

抽象工厂模式

思考抽象工厂模式 抽象工厂专注于产品簇的实现&#xff0c;主要是那些有关联关系的&#xff0c;如果只有一个产品的话就退化成了工厂方法模式 1.抽象工厂模式的本质 抽象工厂模式的本质:选择产品簇的实现。 产品簇&#xff08;Product family&#xff09;指具有相同或相似的功能…

Simulink电机控制代码生成-----关于PI控制器参数整定的一点总结

目录 PI控制器的参数整定方法 方法一&#xff1a; 方法二&#xff1a; 方法对比 总结 看过很多论文&#xff0c;对PI参数的整定方法五花八门&#xff0c;还有PI参数整定的口诀&#xff0c;所谓口诀就是试凑法。除了试凑法&#xff0c;本文提供另外两种方法来整定PI参数&am…

【JavaSE】类与对象--封装性

文章目录一、面向对象的三大特性二、封装性1.什么是封装性&#xff1f;2.为什么要有封装性&#xff1f;3.封装性的作用4.封装性的实现步骤5.访问限定修饰符一、面向对象的三大特性 面向对象的三个基本特征是&#xff1a;封装、继承、多态。 二、封装性 1.什么是封装性&#xf…

BLE学习(4):蓝牙地址类型和设备的隐私

蓝牙地址也被称为蓝牙MAC地址&#xff0c;它能唯一标识一个蓝牙设备的48位的值。在蓝牙规范中&#xff0c;它被称为BD_ADDR。蓝牙的地址类型可以分为两种&#xff1a;public addresses和random addresses&#xff0c;其中random addresses又可再细分为几类&#xff0c;如下图所…

centos7.8手动部署php环境 01 nginx1.8.0编译安装

环境说明&#xff1a; 一、使用电脑 MacBook Pro 二、ssh 工具 finalShell 三、本地虚拟机 VMware Fusion 四、服务器配置 CentOS 7.8 64位 ps&#xff1a;虚拟机安装CentOS省略 登录服务器 一、安装基础工具 yum install net-tools -y yum install wget -y二、将yum源更改为腾…

创新型智慧农业信息化系统建设方案

农业电信增值服务与农村信息化建设要充分考虑新的信息技术、移动通信技术带来的革命&#xff0c;尤其是三网融合、移动互联网、下一代互联网、物联网、云计算、移动支付等新型产品与业务对我们的影响。 存在问题 •网络覆盖基础差 仍然有很多行政村没有宽带网络的覆盖&#x…

TiDB HTAP

TiDB 数据库 HTAP 概述 HTAP技术 OLAP和OLTP带来了多副本的问题。 HTAP的要求 HTAP的架构 异步复制&#xff0c;不参与投票。 HTAP的特性 行列混合 列存支持基于主键的实时更新TiFlash作为列存副本OLTP和OLAP业务隔离 智能选择&#xff08;CBO自动或者人工选择&#xff09;…

IB数学的备战技巧有哪些?

去知名品牌院校进修是许多学员的理想&#xff0c;可是众所周知这些重点大学的入校规定是十分严谨的。因此许多学员为了更好地提高自身的入校概率&#xff0c;就报名参加了一些可以大大加分的国际课程内容和考試。在其中IB数学课则是最受众多学员钟爱的一个课程内容。近些年&…

PyTorch 官方库「上新」,TorchMultimodal 助力多模态人工智能

多模态人工智能是一种新型 AI 范式&#xff0c;是指图像、文本、语音、视频等多种数据类型&#xff0c;与多种智能处理算法相结合&#xff0c;以期实现更高的性能。 近日&#xff0c;PyTorch 官方发布了一个 domain library–TorchMultimodal&#xff0c;用于 SoTA 多任务、多模…

[Linux]----进程间通信之共享内存

文章目录前言一、system V共享内存原理二、创建共享内存关联和去关联共享内存进行通信三、基于管道进行共享内存通信四、临界资源五、信号量总结前言 基于上篇我们利用管道进行进程间通信的使用和实现&#xff0c;本篇将带大家通过共享内存进行进程间通信&#xff01; 正文开始…

R9.8-9.8-9.8-9.8A_特点

R9.8-9.8-9.8-9.8A_特点 R9.8-9.8-9.8-9.8A_特点哈威柱塞泵主要特点是高自吸转速&#xff0c;工作效率高&#xff0c;结构紧凑&#xff0c;工作压力高等。该泵可为工程机械、铲雪车、路堤割草机、柴油叉车、自驱式工作平台和农业机械中的执行元件等提供压力油。 哈威液压泵R9.…

LIRA: Learnable, Imperceptible and Robust Backdoor Attacks 论文笔记

论文信息 论文名称LIRA: Learnable, Imperceptible and Robust Backdoor Attacks作者Khoa Doan(Baidu Research)会议/出版社ICCV 2021pdf&#x1f4c4;在线pdf代码&#x1f4bb;pytorch其他该作者还有一篇攻击的论文&#xff0c;在线pdf 介绍 本文提出了一种新的攻击框架 LIR…

技术中台的定义、范围与内容

【摘要】中台不能算是一个新的概念,只不过把它从一个单一系统扩展到了企业内部所有系统和组织级(企业架构级)的概念。那么时下“中台”的本质究竟是什么?技术中台又该如何理解? 前台、中台、后台的概念早就有之,只不过不同的场景下有不同的内涵。前中后台是从应用系统架…

T293037 [传智杯 #5 练习赛] 白色旅人

题目描述 有一个物品队列 \frak BB&#xff0c;初始时为空。现在共有三种操作。每个操作会给定三个整数 \mathrm{op},x,yop,x,y&#xff0c;其中 \mathrm{op}op 表示操作种类&#xff0c;x,yx,y 是操作的参数。操作分为如下三种&#xff1a; 11&#xff1a;向 \frak BB 尾部添…