不过是一棵红黑树(附源码)

2023-09-20 17:09:00

前言

红黑树,可谓是名号响当当的一种数据结构了。在数据结构学习的初期我们了解到了搜索二叉树,并且知道搜索二叉树的效率是非常之高的,在理想情况下10亿个数据中找一个值它也只需要30次左右,但是它尽管如此厉害可是也有不足的地方,在一些极端情况下,搜索二叉树可能会被退化成一棵单链表,那么此时它的效率就会大打折扣的变成O(n)。

红黑树的性质

在了解搜索二叉树的牛逼和不足之处之后,我们的某个大佬就想,能不能让一棵树无论在什么情况下都保持接近理想状态呢。这是红黑树就由此诞生了。先来看看红黑树的几个性子

1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)


只要满足了这5个条件,那么就能够做到最长路劲不超过最短路劲的二倍,为什么这样说我们通过这样一张图了解一下。

在最极端的情况下,左边连续黑色节点,而右边最多是一红一黑,如果继续往右边添加黑色节点就会违背第4条规则。

红黑树的实现

在了解到红黑树的性质之后,接下来我们就去实现这样效率之高的一棵树。

首先,当我们对一堆数据需要用红黑树的结构来进行管理的时候,那么在对数据进行插入的时候就要按照红黑色树的规则进行处理。假想现在有这样一个场景

假设我们需要在这个位置插入一个值,那么插入的这个值我们应该把它处理成什么颜色呢,如果是红色的话就会违背规则3,如果是黑色的话就会违背规则4。这就好比,今天你必须要犯一个错误,第一个错误呢就是你去把你弟弟的王者卸载掉,而第二个错误就是中午吃饭的时候你把桌子掀掉。这样对比一下呢就是第一个错误你只会让你弟弟不高兴,而第二个错误呢会让一家人都不高兴,孰轻孰重一眼能看出来。所以我在进行插入的时候就选择插入一个红色的节点。

但是回过头一想,如果你让你弟弟不高兴了自己也一天会膈应。所以呢这个时候你的爷爷,爸爸,叔叔这样的长辈就会来劝你两,说哥哥卸载你的游戏是因为你一天作业不做就只顾着玩。然后呢你弟弟在长辈的劝说下又和你重归于好了。

这样呢就既不违背规则又把节点给插入进去了。如果祖父被改成红色之后并且祖父的父亲也是红色,那么我们需要按照这个逻辑继续往上走,让cur成为祖父,parent成为新祖父的父母,grandfather成为parent的父母。

那么问题又来了,如果在爸爸是独生子的情况下,如果按照上面的逻辑将爸爸改成黑色,祖父改成红色

最后就会违背第4条规则,所以当叔叔不存在的时候,我们对其颜色改造之后,在进行旋转一下,让cur做parent的左边,grandfather做parent的右边,这样既不违背搜索二叉树的规则,又不违背红黑树的规则

树的插入可能有无数种情况,但是我们就只需要考虑到这三种情况,因为这三种情况就可以掩盖所有插入的可能,我们通过这张图可以了解到

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
using namespace std;

//节点中的颜色
enum Color
{
	RED,
	BLOCK
};

//红黑树的节点
template<class K,class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Color _color;
	
	RBTreeNode(const pair<K,V> kv,Color col=BLOCK)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		,_color(col)
	{}
};



template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	RBTree()
		:_root(nullptr)
	{}
	bool insert(const pair<K, V> kv)
	{
		//树为空
		if (_root == nullptr)		//如果当前树为空直接插入节点并将颜色改成黑色即可
		{
			_root = new Node(kv);
			_root->_color = BLOCK;
			return true;
		}
		
		//树不为空
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_kv.first > kv.first)	//按照搜索二叉树的特征往合适的位置往下找
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;	//如果需要插入的节点值已经有了就退出
			}
		}

		//找到为空可以插入的位置需要判断是否比当前值大或者小
		cur = new Node(kv);
		cur->_color = RED;
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		//uncle存在并且uncle为红色  如果父亲存在并且父亲的颜色为红色
		while (parent && parent->_color == RED)
		{
			//祖父
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)	//如果parent在grandfather的左边
			{
				Node* uncle = grandfather->_right;

				//uncle存在并且为红色
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLOCK;
					grandfather->_color = RED;

					//继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}

				//uncle不存在
				else
				{
					if (cur == parent->_left)	//如果cur存在的是parent的左边只需要单纯的进行右旋
					{
						//		g
						//    p
						//  c
						RotateR(grandfather);

						grandfather->_color = RED;
						parent->_color = BLOCK;
					}
					else if(cur == parent->_right)//如果cur存在右边就需要进行左右双旋
					{
						//		g
						//   p
						//     c
						RotateL(parent);
						RotateR(grandfather);

						cur->_color = BLOCK;
						grandfather->_color=parent->_color = RED;
					}
				}	
			}

			//如果parent存在grandfather的右边
			else if (parent == grandfather->_right)
			{
				Node* uncle = grandfather->_left;	
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLOCK;
					grandfather->_color = RED;

					//继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}

				//uncle不存在
				else
				{
					if (cur == parent->_right)
					{
						//		g
						//			p
						//				c
						RotateL(grandfather);

						grandfather->_color = cur->_color = RED;
						parent->_color = BLOCK;
					}
					else
					{
						//		g
						//			p
						//		 c
						RotateR(parent);
						RotateL(grandfather);

						cur->_color = BLOCK;
						grandfather->_color = parent->_color = RED;
					}
				}
			}
		}
		_root->_color = BLOCK;	
		return true;
	}


	//左旋
	void RotateL(Node* parent)
	{
		//		p
		//			c
		//				t	
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		parent->_right = curleft;

		//如果cur的左子树不为空将curleft的父亲指向parent
		if (curleft)
			curleft->_parent = parent;

		//让cur的左边指向parent
		cur->_left = parent;
		

		//有可能当前树是另一颗树的子树,只有当前树正在被修复
		Node* ppnode = parent->_parent;

		parent->_parent = cur;
		//如果parent不是另一棵树的子树就直接让cur变成root
		if (parent == _root)
		{
			_root = cur;
			cur->_parent = nullptr;
		}

		//判断parent是ppnode的左子树还是右子树
		else if (ppnode->_left == parent)
		{
			ppnode->_left = cur;
		}
		else if (ppnode->_right == parent)
		{
			ppnode->_right = cur;
		}
		cur->_parent = ppnode;
	}

	//右旋
	void RotateR(Node* parent)
	{
		//		g
		//    p
		//  c
		Node* cur = parent->_left;
		Node* curright = cur->_right;

		parent->_left = curright;

		if (curright)
			curright->_parent = parent;

		cur->_right = parent;

		Node* ppnode = parent->_parent;
		parent->_parent = cur;

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

			cur->_parent = ppnode;
		}
	}

	bool IsBalance()
	{
		return IsBalance(_root);
	}

	bool checkcolor(Node* root,int blocknum,int sum)
	{
		if (root == nullptr)
		{
			if (blocknum != sum)		//如果任意一条线路上的黑色节点和基准值不同就说明路径上的黑色节点不同
			{
				cout << "黑色节点不准确" << endl;
				return false;
			}
			else
			{
				return true;
			}
		}
			
		if (root->_color == BLOCK)	//记录黑色节点
			blocknum++;

		if (root->_color == RED && root->_parent && root->_parent->_color == RED)	//如果当前节点和父亲节点都为红色说明树有问题
		{
			cout << "出现了连续红色节点:" << root->_kv.first  << endl;
			return false;
		}
		
		return checkcolor(root->_left,blocknum,sum)
			&& checkcolor(root->_right, blocknum, sum);
	}
	bool IsBalance(Node* root)
	{
		if (root == nullptr)	
			return true;
		if (root->_color != BLOCK)	//检查第一个节点如果不为黑色就说明出问题了
			return false;

		//统计一条路径上的黑色数量
		int sum = 0;
		Node* num = _root;
		while (num)
		{
			if (num->_color == BLOCK)
				sum++;
			num = num->_left;
		}

		return checkcolor(root,0,sum);
	}

private:
	Node* _root;
};



//测试
#define _CRT_SECURE_NO_WARNINGS 1
#include"RBTree.h"


int main()
{
	//srand((size_t)time(0));
	int arr[] = { 2,8,6,10,14,38,7,9,80,65,72,64,10};
	RBTree<int, int> t;								
	for (auto e : arr)
	{
		t.insert(make_pair(e, e));
	}
	t.IsBalance();
	return 0;
}


更多推荐

python学习之【包和内置模块】

前言接上篇文章python学习之【模块】,这篇文章接着学习python中的包。python中的包包是一种用“点式模块名”构造Python模块命名空间的方法。在包中存放着一些功能相近的模块。包的创建和导入包的创建我们可以在pytarm中创建一个package文件,当我们创建完成后会自动创建一个__init__.py文件,

【Vue+NodeJS】vue路由及NodeJS环境搭建(Windows版)

一、Vue路由1、什么是Vue路由Vue路由是Vue.js框架中用于实现单页面应用(SPA)的路由管理器。它允许您创建多个页面之间的导航,并通过URL的变化来动态加载不同的组件。Vue路由通过声明式的方式定义页面的导航规则,并提供了一些内置的导航组件和功能,如路由链接、路由视图和导航守卫。通过Vue路由,您可以定义不同

数据预处理方式合集

删除空行#delallNonevaluedata_all.dropna(axis=1,how='all',inplace=True)删除空列#delallNonevaluedata_all.dropna(axis=0,how='all',inplace=True)缺失值处理观测缺失值观测数据缺失值有一个比较好用的工具包

蓝桥杯 题库 简单 每日十题 day6

01删除字符题目描述给定一个单词,请问在单词中删除t个字母后,能得到的字典序最小的单词是什么?输入描述输入的第一行包含一个单词,由大写英文字母组成。第二行包含一个正整数t。其中,单词长度不超过100,t小于单词长度。输出描述输出一个单词,表示答案。输入输出样例示例1输入LANQIAO3输出AIAO#include<st

【物联网】常见电子元器件(电阻、电容、电感、二极管、三极管)综合,详细分析原理及其应用

电子元器件是现代电子技术的基础,它们在各个领域中发挥着重要作用。从三极管到电容器、电阻器,这些常用元器件承担着放大、开关、滤波等关键任务。它们的特性和组合方式决定了电路的性能和功能。本文将介绍常用电子元器件的工作原理和应用场景,帮助读者更好地理解和运用它们。无论是电子爱好者还是专业工程师,对于电子元器件的了解都是必不可

走进人工智能| 智能物联网 AIoT的魅力交织

前言:AI+IoT是指人工智能(AI)与物联网(IoT)的结合。智能物联网是一种技术体系,通过连接和集成物理设备、传感器和互联网,实现设备之间的智能交互和数据共享,为人们提供智能化、自动化和高效化的生活和工作体验。文章目录序言背景领跑巨头核心技术支持目前形式发展的困难点总结序言智能物联网(InternetofThing

通信相关常识

MSISDN手机号“MSISDN”是一个移动电话号码的标识,它是国际移动用户识别码(IMSI)和移动国家代码(MCC)以及移动网络代码(MNC)的组合。MSISDN是一个唯一的电话号码,用于标识移动设备在移动通信网络中的位置和身份。它通常由国家码(国家的电话区号)和用户号码(用户的本地号码)组成。例如,一个常见的MSI

任意文件的上传和下载

1.任意文件下载(高危)定义一些网站由于业务需求,往往需要提供文件查看或文件下载功能,但若对用户查看或下载的文件不做限制,则恶意用户就能够查看或下载任意敏感文件,这就是文件查看与下载漏洞。可以下载(参数没有限制)可以通过../上一级的形式猜测下载配置文件,获取系统的账号密码挖洞的方式一般链接形式:download.ph

现今主流物联网无线通信技术分类详解

无线技术正在迅速发展,并在人们的生活中发挥越来越大的作用。而随着无线应用的增长,各种技术和设备也会越来越多,也越来越依赖于无线通信技术。本文盘点下物联网中无线通信主要的技术。一、无线通信技术的几大主流分类1.美国通信委员会(FCC)分类2015年,美国通信委员会(FCC,FederalCommunicationsCom

vue若依前端项目搭建

1.项目搭建首先进入到你需要创建的项目目录下面,然后输入命令vuecreate.创建项目接下来选择手动搭建,然后把下面图片中的内容选上再然后继续配置一些参数信息接下来运行npmrunserve项目就启动起来了2.配置登录界面文件首先修改src/router/index.js这个界面,写若依的登录界面先把这一串内容删除掉

Linux 应用程序日志查看命令

目录前言需求命令1.tail命令2.head命令3.cat命令4.sed5.grep命令6.more命令7.less命令前言在工作过程中,需要查看服务端的日志,掌握常用的命令是开发工程师必备的技能,快速的查看到日志,才能精准定位问题所在。需求1.查看日志文件需求主要有以下几个服务启动后跟踪服务日志是否启动正常服务运行过

热文推荐