【数据结构】手撕红黑树

news2024/9/24 19:18:56


目录

一、红黑树简介

1、红黑树的简介

2、红黑树的性质

二、红黑树的插入(看叔叔的颜色就行)

1、为什么新插入的节点必须给红色?

2、插入红色节点后,判定红黑树性质是否被破坏

2.1情况一:uncle存在且为红

2.2情况二:uncle不存在/存在且为黑(直线)

2.3情况三:uncle不存在/存在且为黑(折线)

2.4总结

3、红黑树插入代码

三、红黑树整体代码


一、红黑树简介

1、红黑树的简介

红黑树和AVL树一样,因其逻辑复杂,面试时现场要求手撕就是纯纯刁难面试者。但某大厂面试官曾要求某些求职者现场手撕红黑树(我赌5毛,让面试官撕,他也撕不出来,而且你家员工上班手搓红黑树啊?),随后求职遭遇被发到网上吐槽,这便有了“手撕红黑树”的梗,也让红黑树成为了知名度最高的数据结构。(话虽如此,对于红黑树的性质、插入思想等概念还是需要掌握的)

2、红黑树的性质

红黑树本质也是一种二叉搜索树。底层结构需要使用二叉搜索树的地方,基本上都会使用红黑树来实现,而AVL树也因此坐上了冷板凳。

红黑树通过在每个节点上添加一个存储位,用于存储“RED”或“BLACK”。通过节点上红/黑颜色限制,确保最长路径不超过最短路径的两倍,因而它是接近平衡的树形结构。最短路径:全黑;最长路径:一黑一红交替。

1、红黑树的根节点是黑色的;

2、没有连续的红色节点(如果某个节点为红色,则它的左右孩子必须是黑色)

3、无论哪个节点,其每条路径的黑色节点数量相同;

4、所有的空节点(NIL节点)可以认为是黑色的。

最优情况:全黑或每条路径都是一黑一红的满二叉树,高度logN

最差情况:每颗子树左子树全黑,右子树一黑一红。高度2*logN。

可以发现,最坏情况的时间复杂度和AVL树一样,都是O(logN),但是红黑树这种近似平衡的结构减少了大量旋转,综合性能优于AVL树。

二、红黑树的插入(看叔叔的颜色就行)

1、为什么新插入的节点必须给红色?

新节点给红色,可能会违反上面说的红黑树性质2;如果新节点给黑色,必定会违反性质3。

2、插入红色节点后,判定红黑树性质是否被破坏

情况一调整后可能变成情况一、情况二、情况三。

2.1情况一:uncle存在且为红

这种情况cur、parent、grandfather都是确定颜色的,唯独uncle的颜色是不确定的。

可以这么想:cur为红那么就需要将parent变为黑;parent变黑需要控制每条路径上黑节点的数量相同,那么就要把uncle变黑;如果grandfather不是根,需要反转为红,用以控制路径黑节点数量相同。继续向上调整即可。

2.2情况二:uncle不存在/存在且为黑(直线)

uncle的情况分两种。

uncle不存在,则cur为插入节点,单旋即可。

uncle存在且为黑是第一种情况变过来的。

2.3情况三:uncle不存在/存在且为黑(折线)

uncle的情况分两种。

uncle不存在,则cur为插入节点,两次单旋即可。

uncle存在且为黑,先掰直

2.4总结

插入新节点时,父节点为红,看叔叔的颜色。

1、叔叔存在且为红,变色,向上调整(可能变为三种情况中的任意一种)

2、叔叔不存在/存在且为黑,直线。单旋+变色

3、叔叔不存在/存在且为黑,折线,两次单旋+变色

3、红黑树插入代码

bool Insert(const pair<K,V>& kv)
{
    if (_root == nullptr)
    {
        _root = new Node(kv);
        _root->_col = BLACK;//根节点给黑色
        return true;
    }
    //_root不为空
    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;//维护cur的父指针
    }
    else
    {
        parent->_left = cur;
        cur->_parent = parent;
    }
    //调整
    while (parent&&parent->_col == RED)
    {
        Node* grandfather = parent->_parent;//找到祖父
        if (grandfather->_left == parent)//如果父亲是祖父的左孩子
        {
            Node* uncle = grandfather->_right;//找到叔叔
            //情况一:叔叔存在且为红
            if (uncle != nullptr && uncle->_col == RED)
            {
                //变色
                parent->_col = uncle->_col = BLACK;
                grandfather->_col = RED;
                
                cur = grandfather;
                parent = cur->_parent;
            }
            else//情况二或情况三
            {
                if (cur == parent->_left)//情况二,直线
                {
                    RotateRight(grandfather);//右单旋
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                }
                else//情况三,折线
                {
                    RotateLeft(parent);//左单旋
                    RotateRight(grandfather);//右单旋
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                break;
            }
        }
        else//如果父亲是祖父的右孩子
        {
            Node* uncle = grandfather->_left;
            if (uncle != nullptr && uncle->_col == RED)
            {
                parent->_col =uncle->_col= BLACK;
                grandfather->_col = RED; 

                cur = grandfather;
                parent = cur->_parent;
            }
            else
            {
                if (cur == parent->_right)//情况二,直线
                {
                    //g
                    //  p
                    //    c
                    RotateLeft(grandfather);//左单旋
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                }
                else//情况三,折线
                {
                    //g
                    //  p
                    //c   
                    RotateRight(parent);//右单旋
                    RotateLeft(grandfather);//左单旋
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                break;
            }
        }
    }
    _root->_col=BLACK;
    return true;
}

三、红黑树整体代码

#pragma once
#include <iostream>
#include <map>
#include <set>
#include <string>
using namespace std;
enum Color
{
	RED,
	BLACK,
};
template <class K,class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K,V>& kv)
		:_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_kv(kv)
		,_col(RED)
	{}
	RBTreeNode<K,V>* _parent;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	pair<K, V> _kv;
	Color _col;
};
template <class K, class V>
class RBTree
{
public:
	typedef RBTreeNode<K,V> Node;
	bool Insert(const pair<K,V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;//根节点给黑色
			return true;
		}
		//_root不为空
		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;//维护cur的父指针
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		//调整
		while (parent&&parent->_col == RED)
		{
			Node* grandfather = parent->_parent;//找到祖父
			if (grandfather->_left == parent)//如果父亲是祖父的左孩子
			{
				Node* uncle = grandfather->_right;//找到叔叔
				//情况一:叔叔存在且为红
				if (uncle != nullptr && uncle->_col == RED)
				{
					//变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					
					cur = grandfather;
					parent = cur->_parent;
				}
				else//情况二或情况三
				{
					if (cur == parent->_left)//情况二,直线
					{
						RotateRight(grandfather);//右单旋
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//情况三,折线
					{
						RotateLeft(parent);//左单旋
						RotateRight(grandfather);//右单旋
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else//如果父亲是祖父的右孩子
			{
				Node* uncle = grandfather->_left;
				if (uncle != nullptr && uncle->_col == RED)
				{
					parent->_col =uncle->_col= BLACK;
					grandfather->_col = RED; 

					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)//情况二,直线
					{
						//g
						//  p
						//    c
						RotateLeft(grandfather);//左单旋
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//情况三,折线
					{
						//g
						//  p
						//c   
						RotateRight(parent);//右单旋
						RotateLeft(grandfather);//左单旋
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col=BLACK;
		return true;
	}
	void Inorder()
	{
		_Inorder(_root);
	}
	bool IsBalance()
	{
		return _IsBalance();
	}
private:
	void _Inorder(Node* root)
	{
		if (root == nullptr)
			return;

		_Inorder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_Inorder(root->_right);
	}
	bool Check(Node* root)
	{
		if (root == nullptr)
			return true;
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "父子均为红" << endl;
			return false;
		}
		return Check(root->_left) && Check(root->_right);
	}
	bool _IsBalance()
	{
		if (_root == nullptr)
			return true;
		if (_root->_col != BLACK)
		{
			return false;
		}
		return Check(_root);
	}
	void RotateLeft(Node* parent)//左单旋
	{
		Node* grandfather = parent->_parent;
		Node* cur = parent->_right;
		if (parent == _root)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (grandfather->_left == parent)//需要判定parent原来属于grandfather的哪一边
				grandfather->_left = cur;
			else
				grandfather->_right = cur;
			cur->_parent = grandfather;
		}
		parent->_right = cur->_left;
		if (cur->_left != nullptr)
			cur->_left->_parent = parent;
		cur->_left = parent;
		parent->_parent = cur;
	}
	void RotateRight(Node* parent)//右单旋
	{
		Node* grandfather = parent->_parent;
		Node* cur = parent->_left;
		if (parent == _root)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (grandfather->_left == parent)
			{
				grandfather->_left = cur;
				cur->_parent = grandfather;
			}
			else
			{
				grandfather->_right = cur;
				cur->_parent = grandfather;
			}
		}
		parent->_parent = cur;
		parent->_left = cur->_right;
		if (cur->_right != nullptr)
			cur->_right->_parent = parent;
		cur->_right = parent;
	}
private:
	Node* _root=nullptr;
};
void TestAVLTree()
{
	//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	//int a[] = { 9,8,7,6,5,4,3,2,1};
	RBTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
	}

	t.Inorder();

	//cout << t.IsBalance() << endl;
}

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

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

相关文章

微信商城小程序怎么做_分享实体店做微信商城小程序制作步骤

各行各业都在用微商城小程序开店&#xff0c;不管是餐饮店还是便利店&#xff0c;还是五金店。都是可以利用微信小程序开一个线上店铺。实现线上跟线下店铺更加全面的结合。维护好自己的老客户。让您的客户给您拉新&#xff0c;带来新客户。小程序经过这几年的快速发展和不断升…

【量化回测必看!】Backtrader保姆级教学+免费行情源 框架介绍

前言 想开始量化学习不知道如何入手&#xff1f;市面上的学习资料太多不知道该怎么看&#xff1f; 博主将从零基础讲解回测框架&#xff0c;一步步完成量化数据源的搭建&#xff0c;让你10天内成为量化高手 博主同时将视频课程内容在B站更新&#xff0c;可以关注“量化NPC”获…

学习 Python 之 Pygame 开发魂斗罗(五)

学习 Python 之 Pygame 开发魂斗罗&#xff08;五&#xff09;继续编写魂斗罗1. 加载地图2. 修改角色尺寸和地面高度继续编写魂斗罗 在上次的博客学习 Python 之 Pygame 开发魂斗罗&#xff08;四&#xff09;中&#xff0c;我们完成了角色的移动和跳跃还有射击&#xff0c;由…

Redis源码---整体架构

目录 前言 Redis目录结构 前言 deps目录 src 目录 tests 目录 utils 目录 重要的配置文件 Redis 功能模块与源码对应 前言 服务器实例 数据库数据类型与操作 高可靠性和高可扩展性 辅助功能 前言 以先面后点的方法推进无特殊说明&#xff0c;都是基于 Redis 5.0.…

AI Codec,视频模板技术,高效视频处理,RTC+AI,感知编码,CV-CUDA,窄带高清AI...

AI Codec&#xff0c;NPU硬件加速Topic《基于AI和NPU的Codec变革》孔德辉 中兴微电子 多媒体技术总监伴随通信容量&#xff08;包括5G以及千兆有线网络&#xff09;的发展&#xff0c;高带宽为更多用户接入超高清视频提供了可能。但是随着用户数量的增加&#xff0c;高质量的压…

排序基础之选择排序法

目录 前言 一、什么是选择排序 二、实现选择排序 三、使用泛型扩展 四、使用自定义类型测试 前言 今天天气不错&#xff0c;这么好的天气不干点啥实在是有点可惜了&#xff0c;于是乎&#xff0c;拿出键盘撸一把&#xff01; 来&#xff0c;今天来学习一下排序算法中的选…

港科夜闻|全国政协副主席梁振英先生率香港媒体高管团到访香港科大(广州)...

关注并星标每周阅读港科夜闻建立新视野 开启新思维1、全国政协副主席梁振英先生率香港媒体高管团到访香港科大(广州)。2月21日下午&#xff0c;在全国政协副主席、广州南沙粤港合作咨询委员会顾问梁振英先生的带领下&#xff0c;香港20余家媒体的高管及知名媒体人士到访香港科大…

电脑技巧:分享8个Win11系统必备小技巧

目录 1、让任务栏显示“右键菜单” 2、任务栏置顶 3、还原经典右键菜单 4、Win11版任务管理器 5、新版AltTab 6、开始菜单不再卡 7、为Edge浏览器添加云母效果 8、自动切换日/夜模式 Win11在很多地方都做了调整&#xff0c;但由于涉及到诸多旧有习惯&#xff0c;再加上…

SRE中 的SLO,SLI等知识 归纳

SLA Service Level Agreement 服务质量/水平协议SLO Service Level Objective 服务质量/水平目标SLI Services Level Indicator 服务质量/水平指标下面用人、事、物的逻辑进行阐释。人和事用从上到下&#xff0c;从左到右的顺序。客户 - 每 1 个客户在使用产品服务时&…

gin 框架初始教程

一 、gin 入门1. 安装gin &#xff1a;下载并安装 gin包&#xff1a;$ go get -u github.com/gin-gonic/gin2. 将 gin 引入到代码中&#xff1a;import "github.com/gin-gonic/gin"3.初始化项目go mod init gin4.完整代码package mainimport "github.com/gin-go…

JavaSE10-循环语句(for、while、do...while)

文章目录一、for循环1.格式2.执行流程二、while循环1.格式三、do...while循环1.格式四、循环控制(break、continue)1.break2.continue五、案例1.请输出下列的形状2.打印99乘法表一、for循环 1.格式 初始化语句只有在最开始的时候执行了一次如果第一次进行条件判断的时候结果为…

测试开发 | 视频编辑SDK测试

短视频编辑SDK测试有一段时间了&#xff0c;因此抽时间对编辑SDK的相关内容进行简要复盘。 功能说明 短视频编辑SDK支持gif&#xff0c;不同格式的图片&#xff0c;视频文件的拼接导入&#xff0c;编辑&#xff0c;添加特效&#xff0c;合成导出等功能。更具体的介绍可以参照…

Elasticsearch:如何轻松安全地对实时 Elasticsearch 索引重新索引你的数据

在很多的时候&#xff0c;由于一些需求&#xff0c;我们不得不修改索引的映射&#xff0c;也即 mapping&#xff0c;这个时候我们需要重新索引&#xff08;reindex&#xff09;来把之前的数据索引到新的索引中。槽糕的是&#xff0c;我们的这个索引还在不断地收集实时数据&…

Python3-数字

Python3 数字(Number) Python 数字数据类型用于存储数值。 数据类型是不允许改变的,这就意味着如果改变数字数据类型的值&#xff0c;将重新分配内存空间。 Python 支持三种不同的数值类型&#xff1a; 整型(int) - 通常被称为是整型或整数&#xff0c;是正或负整数&#x…

8.Spring Security 权限控制

1.简介入门JavaEE和SpringMVC &#xff1a;Spring Security就是通过11个Fliter进行组合管理小Demouser实体类user.type字段&#xff0c;0普通用户&#xff0c;1超级管理员&#xff0c;2版主补全get set tostringimplement UserDetails&#xff0c;重写以下方法// true: 账号未过…

_improve-2

-------------------- 左边定宽&#xff0c;右边自适应方案 float margin&#xff0c;float calc /* 方案1 */ .left {width: 120px;float: left; } .right {margin-left: 120px; } /* 方案2 */ .left {width: 120px;float: left; } .right {width: calc(100% - 120px);fl…

【深度学习】线性回归、逻辑回归、二分类,多分类等基础知识总结

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言1. 线性回归2、逻辑回归3. 单层神经元的缺陷&多层感知机softmax 多分类前言 入行深度学习快2年了,是时间好好总结下基础知识了.现在看可能很多结论和刚学的…

SystemVerilog-时序逻辑建模(5)多个时钟和时钟域交叉

数字硬件建模SystemVerilog-时序逻辑建模&#xff08;5&#xff09;多个时钟和时钟域交叉数字门级电路可分为两大类&#xff1a;组合逻辑和时序逻辑。锁存器是组合逻辑和时序逻辑的一个交叉点&#xff0c;在后面会作为单独的主题处理。组合逻辑描述了门级电路&#xff0c;其中逻…

办公室人员离岗识别检测系统 yolov7

办公室人员离岗识别检测系统根据yolov7网络模型深度学习技术&#xff0c;办公室人员离岗识别检测算法能够7*24小时全天候自动识别人员是否在岗位。YOLOv7 在 5 FPS 到 160 FPS 范围内&#xff0c;速度和精度都超过了所有已知的目标检测器&#xff0c;并在V100 上&#xff0c;30…

2023/2/26 Vue学习笔记 配置代理解决跨域[CORS ]的问题

利用vue的脚手架巧妙的解决ajax跨域的问题 1 我们首先利用springboot服务搭建 注意这里引出了跨域[CORS ]的问题: Access to XMLHttpRequest at http://localhost:5000/getUserInfo from origin http://localhost:8080 has been blocked by CORS policy: No Access-Control-A…