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

这篇具有很好参考价值的文章主要介绍了【数据结构】简单快速过一遍红黑树。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


红黑树

1 红黑树的概念

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

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

2 红黑树的性质

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

  2. 根节点是黑色的

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

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

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

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

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

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


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)

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

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

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

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

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

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


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树传送门文章来源地址https://www.toymoban.com/news/detail-420768.html


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;
	}
}

到了这里,关于【数据结构】简单快速过一遍红黑树的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • MongoDB简单快速入门

     MongoDB是一个开源、高性能、无模式的文档型数据库。NoSQL数据库产品中的一种,是最想关系型数据库的非关系型数据库  直接将安装的压缩包进行解压,然后在创建一个data文件夹,在data文件夹下面创建一个子文件夹db 启动服务端 创建数据库 mongod --dbpath=…datadb 进入bin目

    2024年02月08日
    浏览(35)
  • Vue 如何简单快速组件化

    为了简化拆分复杂的代码逻辑,和实现代码的组件化,封闭化。我们需要使用组件化的方法。我这里只讲解我感觉最优的组件化方法。 vue组件化总结 vue 单文件组件 子组件修改Props报错 vue 父组件调用子组件方法ref vue中组件的props属性(详) 使用Vue-cil搭建一个简单的Vue页面,

    2024年02月12日
    浏览(30)
  • gitlab使用docker简单快速部署

    GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的web服务。本文主要用来记录如何使用docker快速搭建gitlab服务。 GitLab是由GitLabInc.开发,使用MIT许可证的基于网络的Git仓库管理工具,且具有wiki和issue跟踪功能。使用Git作为代码管理

    2024年02月03日
    浏览(38)
  • Ubuntu访问主机共享文件夹——简单快速

    首先点击此处 点击设置或者虚拟机设置弹出下面的页面,点击共享文件夹 点击下面确定就行了。 进入Ubuntu中打开终端,输入如下命令就可以了。 cd /mnt/hgfs

    2024年02月11日
    浏览(30)
  • 超详细python下简单快速下载opencv

    ​看了许多下载安装opencv的方法,总结出一个最高效简单的,希望一些像我这样的小白可以少踩雷(主要怕自己忘记!) python下载 大家可以到官网中选择需要的版本,官网地址 ,官网下载调试pip比较麻烦,图省事的UU们可以私信我,如果看到可以给大家发压缩包,直接使用

    2024年02月17日
    浏览(31)
  • LTspice快速上手--搭建简单RC电路

    LTspice下载地址 打开LTspice: 单击File - New Schematic或者直接点击菜单栏中New Schematic新建原理图。 点击Edit - Component放置元器件或者直接点击菜单栏中的元器件按钮进行放置。     【技巧】 按键盘F2可快速打开元器件窗口进行放置。  放置电源、电阻、电容和GND。 元器件放置时

    2024年01月16日
    浏览(35)
  • 冒泡排序 简单选择排序 插入排序 快速排序

    bubblesort 两个for循环,从最右端开始一个一个逐渐有序 selectsort 假设是升序,两个for循环,从最左端开始一个一个逐渐有序,找到lengh-1个无序区的最小值 insertsort 两个for循环,从最左端开始一个一个逐渐有序,默认第一个就是有序区,第一个for遍历无序区,第二个for循环遍历

    2024年02月13日
    浏览(37)
  • Windows重启mysql的方法(快速简单)

    目录 一、背景 二、操作步骤  错误做法  正确做法 有时候修改了数据库,但是MySQL数据库内容有延迟缓存,那么就需要重启一下数据库去解决问题 错误做法 直接去cmd命令里面输入 net stop mysql 这样停止,这样很可能会失败,因为mysql数据库服务里面不一定叫mysql,可能还加了

    2024年04月16日
    浏览(28)
  • Java简单快速入门JWT(token生成与验证)

            简单来说,token就是一个将信息加密之后的密文,而jwt也是token的实现方式之一,用于服务器端进行身份验证和授权访问控制。由于是快速入门,这里简单介绍一下jwt的生成原理         jwt由三部分组成。分别是                 1.Header(标头),一般用于指明toke

    2024年02月04日
    浏览(55)
  • python安装opencv的方法(快速简单安装)

    Anaconda 下创建一个python3.6的虚拟环境,进入虚拟环境开始安装: 先安装 opencv-python 安装过程: 然后安装opencv-contrib-python 安装过程 此种方法安装后,import cv2 as cv后,代码可以正常运行

    2024年02月16日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包