当前位置: 首页 > article >正文

【数据结构】简单快速过一遍红黑树

文章目录

  • 红黑树
    • 1 红黑树的概念
    • 2 红黑树的性质
    • 3 红黑树节点的定义
    • 4 红黑树的插入操作
    • 5 红黑树的验证
    • 6 红黑树与AVL树的比较
    • 7.C++实现红黑树


红黑树

1 红黑树的概念

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

image-20230404133219686

2 红黑树的性质

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

  2. 根节点是黑色的

  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的

  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点

  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

  • 举个极端一点的例子就清楚了,树的一边全是黑节点,另一边是一黑一红交替,那么全黑的那一边一定是最短的那个,一黑一红交替一定是最长的那个,这时候刚好是2倍关系

image-20230404171718935


3 红黑树节点的定义

enum Color
{
	RED,//red红色
	BLACK,//black黑色
};

template<class T>
struct RBTreeNode
{
	//在节点的定义中,为什么要将节点的默认颜色给成红色的?---因为这样违反的红黑树规则最少,方便处理
	RBTreeNode(const T& val,Color color=RED)
		:_val(val), _left(nullptr), _right(nullptr), _parent(nullptr), _color(color)
	{}

	T _val;// 节点的值
	RBTreeNode<T>* _left;// 节点的左孩子
	RBTreeNode<T>* _right;// 节点的右孩子
	RBTreeNode<T>* _parent;// 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)
	Color _color;// 节点的颜色
};

4 红黑树的插入操作

红黑树同样也是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  1. 按照二叉搜索的树规则插入新节点
  2. 检测新节点插入后,红黑树的性质是否造到破坏
    因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何
    性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连
    在一起的红色节点,此时需要对红黑树分情况来讨论:
    约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
    • 情况一: cur为红,p为红,g为黑,u存在且为红 ----p(parent),g(grandparent),u(uncle)

image-20230404194800048

cur和p均为红,违反了性质三,此处能否将p直接改为黑?—不能
解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

  • 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

image-20230404200017733

  • 情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑

image-20230404200756753


5 红黑树的验证

红黑树跟AVL树的代码一样复杂,那我们怎么知道自己写的代码有没有问题呢?

同样也可以写一个小代码来检验一下是否符合红黑树性质即可

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)

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

bool IsValidRBTree()
{
	if (_root == nullptr)//空树也是红黑树
		return true;
	if (_root->_color != BLACK)
	{
		cout << "违反了红黑树性质二:根节点必须为黑色" << endl;
		return false;
	}
	//获取任意一个节点的黑色节点
	size_t blackCount = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_color == BLACK)
			blackCount++;
		cur = cur->_left;
	}
	// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
	size_t k = 0;
	return IsValidRBTree(_root, blackCount, k);
}
bool IsValidRBTree(Node* root, size_t blackCount, size_t k)
{
	//走到null之后,判断k和black是否相等
	if (root == nullptr)
	{
		if (k != blackCount)
		{
			cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
			return false;
		}
		return true;
	}
	// 统计黑色节点的个数
	if (root->_color == BLACK)
		k++;
	// 检测当前节点与其双亲是否都为红色
	Node* parent = root->_parent;
	if (parent && parent->_color == RED&& root->_color == RED)
	{
		cout << "违反性质三:不能存在连在一起的红色节点" << endl;
		return false;
	}
	return IsValidRBTree(root->_left, blackCount, k) &&
		IsValidRBTree(root->_right, blackCount, k);
}


6 红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

总之一句话:

实际应用中,若搜索的次数远远大于插入和删除,那么选择AVL,如果搜索,插入删除次数几乎差不多,应该选择红黑树。

AVL树传送门


7.C++实现红黑树

#pragma once 
#include <iostream>
#include <ctime>
using namespace std;

namespace hdm
{
	enum Color
	{
		RED,//red红色
		BLACK,//black黑色
	};

	template<class T>
	struct RBTreeNode
	{
		//在节点的定义中,为什么要将节点的默认颜色给成红色的?---因为这样违反的红黑树规则最少,方便处理
		RBTreeNode(const T& val, Color color = RED)
			:_val(val), _left(nullptr), _right(nullptr), _parent(nullptr), _color(color)
		{}

		T _val;// 节点的值
		RBTreeNode<T>* _left;// 节点的左孩子
		RBTreeNode<T>* _right;// 节点的右孩子
		RBTreeNode<T>* _parent;// 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)
		Color _color;// 节点的颜色
	};
	


	template<class T>
	class RBTree
	{
	public:
		typedef RBTreeNode<T> Node;

		bool Insert(const T& x)
		{
			if (_root == nullptr)
			{
				_root = new Node(x);
				_root->_color = BLACK;//规定根节点都是黑色
				return true;
			}

			Node* cur = _root;
			Node* parent = cur;//记录parent方便后续插入
			while (cur)
			{
				//cur往子树走的同时记录子树的父节点
				if (cur->_val > x)//比cur小往左子树找
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_val < x)//比cur大往右子树找
				{
					parent = cur;
					cur = cur->_right;
				}
				else//进入else说明cur->_val==x,表示已经存在该节点,不需要插入,返回false表示插入失败
				{
					return false;
				}
			}

			//程序走到这说明cur=nullptr,最后x就应该要插入在parent的下面,至于是插入左边还是右边
			//具体看x与parent->_val 之间值的关系
			cur = new Node(x);
			cur->_color = RED;//默认插入红色节点
			if (parent->_val > x)//如果x的值小于parent就往左边插入
			{
				parent->_left = cur;
			}
			else//如果x的值大于parent就往右边插入
			{
				parent->_right = cur;
			}
			cur->_parent = parent;

			//判断是否符合红黑树规则,没有则要改颜色
			while (parent&& parent->_color == RED)
			{
				//大致分两个大种情况:
				//1.叔叔节点存在且为红---看图
				//2.叔叔节点不存在或者存在且为黑
				
				Node* grandparent = parent->_parent;
				if (grandparent->_left == parent)//确定叔叔节点的位置
				{
					Node* uncle = grandparent->_right;
					if (uncle && uncle->_color == RED)
					{
						//情况一:叔叔节点存在且为红
						//处理方式:变色处理
						parent->_color = uncle->_color = BLACK;
						grandparent->_color = RED;

						//这种情况因为改了祖父节点的颜色有可能影响到其他节点,所以要继续向上调整
						cur = grandparent;
						parent = cur->_parent;
					}
					else
					{
						//有可能是uncle节点不存在,也有可能是uncle是黑节点---对应就是情况二和三
						
						//uncle节点可能不存在或者为黑节点
						if (parent->_left == cur)//cur与parent是同侧
						{
							//右旋+变色
							RotateR(grandparent);
							parent->_color = BLACK;
							grandparent->_color = RED;
						}
						else//cur和parent不是同一侧--情况三
						{
							//左右旋+变色
							RotateL(parent);
							RotateR(grandparent);
							cur->_color = BLACK;
							grandparent->_color = RED;
						}
						break;
					}
				}
				else//grandparent->_right == parent---其实跟上面的情况类似,只不过方向相反
				{
					Node*uncle = grandparent->_left;
					if (uncle&& uncle->_color == RED)//情况一:叔叔节点存在且为空
					{
						uncle->_color = parent->_color = BLACK;
						grandparent->_color = RED;

						//继续向上调整
						cur = grandparent;
						parent = cur->_parent;
					}
					else
					{
						//可能是情况二或者三

						if (parent->_right == cur)//当cur与parent是同侧的时候
						{
							//左旋+变色
							RotateL(grandparent);
							parent->_color = BLACK;
							grandparent->_color = RED;
						}
						else//不同侧
						{
							//右左旋转+变色
							RotateR(parent);
							RotateL(grandparent);
							cur->_color = BLACK;
							grandparent->_color = RED;
						}
						break;
					}
				}	
			}

			_root->_color = BLACK;//因为上面的操作有可能把跟节点变红,不管怎么样直接加这句代码就不用管它不可能出现根是红的情况

			return true;
		}
		void InorderTree()
		{
			InorderTree(_root);
			cout << endl;
		}

		bool IsValidRBTree()
		{
			if (_root == nullptr)//空树也是红黑树
				return true;
			if (_root->_color != BLACK)
			{
				cout << "违反了红黑树性质二:根节点必须为黑色" << endl;
				return false;
			}
			//获取任意一个节点的黑色节点
			size_t blackCount = 0;
			Node* cur = _root;
			while (cur)
			{
				if (cur->_color == BLACK)
					blackCount++;
				cur = cur->_left;
			}
			// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
			size_t k = 0;
			return IsValidRBTree(_root,blackCount,k);
		}

	private:

		bool IsValidRBTree(Node* root,size_t blackCount,size_t k)
		{
			//走到null之后,判断k和black是否相等
			if (root == nullptr)
			{
				if (k != blackCount)
				{
					cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
					return false;
				}
				return true;
			}
			// 统计黑色节点的个数
			if (root->_color == BLACK)
				k++;
			// 检测当前节点与其双亲是否都为红色
			Node* parent = root->_parent;
			if (parent && parent->_color == RED&& root->_color == RED)
			{
				cout << "违反性质三:不能存在连在一起的红色节点" << endl;
				return false;
			}
			
			return IsValidRBTree(root->_left,blackCount,k) && 
					IsValidRBTree(root->_right,blackCount,k);
		}

		void InorderTree(Node* root)
		{
			if (root == nullptr)
				return;
			InorderTree(root->_left);
			cout << root->_val << " ";
			InorderTree(root->_right);
		}
		void RotateL(Node* parent)//左旋
		{
			Node* subR = parent->_right;
			Node* subRL = subR->_left;

			Node* pparent = parent->_parent;
			if (subRL)//如果存在subRL,就把它的父子关系连上
				subRL->_parent = parent;
			parent->_right = subRL;
			subR->_left = parent;
			parent->_parent = subR;

			if (pparent == nullptr)//pparent为空表示parent为根节点
			{
				_root = subR;
				_root->_parent = nullptr;
			}
			else
			{
				//pparent存在,要判断parent之前是位置pparent的那一边
				if (pparent->_left == parent)
				{
					pparent->_left = subR;
				}
				else
				{
					pparent->_right = subR;
				}
			}
			subR->_parent = pparent;
		}

		void RotateR(Node* parent)//右旋
		{
			Node* subL = parent->_left;
			Node* subLR = subL->_right;

			Node* pparent = parent->_parent;

			if (subLR)//如果存在subLR,就把它的父子关系连上
				subLR->_parent = parent;
			parent->_left = subLR;
			subL->_right = parent;
			parent->_parent = subL;
			if (pparent == nullptr)//说明parent是根节点
			{
				_root = subL;
				_root->_parent = nullptr;
			}
			else
			{
				//pparent存在,要判断parent之前是位置pparent的那一边
				if (pparent->_left == parent)
				{
					pparent->_left = subL;
				}
				else
				{
					pparent->_right = subL;
				}
			}
			subL->_parent = pparent;
		}
	private:
		Node* _root=nullptr;
	};


	void RBTreeTest1()
	{
		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 };
		RBTree<int> t;
		for (auto e : a)
		{
			if (e == 13)
				int i = 0;
			t.Insert(e);
		}
		t.InorderTree();
	}


	void RBTreeTest2()
	{
		srand(time(0));
		const size_t N = 10000;
		RBTree<int> t;
		for (size_t i = 0; i < N; ++i)//用1w个随机数测试插入是否符合红黑树规则
		{
			size_t x = rand();
			t.Insert(x);
			//cout << t.IsBalance() << endl;
		}

		t.InorderTree();

		cout << t.IsValidRBTree() << endl;
	}
}

http://www.kler.cn/a/15358.html

相关文章:

  • 2.5D视觉——Aruco码定位检测
  • Ubuntu安装配置MySQL(远程登录)
  • PyTorch实战-手写数字识别-单层感知机
  • hadoop+wsl 10.255.255.254,BlockMissingException: Could not obtain block: 踩坑
  • “乐鑫组件注册表”简介
  • Gin 框架入门(GO)-1
  • FIR半带滤波器
  • 9.Java中异常处理机制是什么
  • Java 创建线程池的三种方式
  • 九耶丨阁瑞钛伦特-Java分布式(一)
  • MATLAB算法实战应用案例精讲-【智能优化算法】哈里斯鹰(HHO)(论文篇二)
  • 集成灶/小家电语音提示芯片方案-WTN6040-8S唯创知音自主研发
  • Android类似微信聊天页面教程(Kotlin)四——数据本地化
  • leetcode657. 机器人能否返回原点
  • 移动通信网络频段大全
  • OJ练习第91题——文本左右对齐
  • 3.7 Linux shell脚本编程(分支语句、循环语句)
  • C语言必背经典程序代码
  • 【VM服务管家】VM4.0软件使用_1.3全局模块类
  • Shiro相关知识
  • 获取Tree 树形控件下面的全部id
  • 小米青春版路由器刷openwrt
  • UE5实现Runtime环境下绘制点功能
  • 201709-1 打酱油
  • 基于dsp+fpga+AD+ENDAC的半导体运动台高速数据采集电路仿真设计(四)
  • Qt5.9学习笔记-事件(一)