红黑树的实现

目录

1.红黑树的概念

2.红黑树的规则

3.红黑树的效率

4.红黑树的实现

红黑树的节点

红黑树的插入

情况1.变色,继续向上处理

情况2:单旋+变色

右单旋+变色

左单旋+变色

情况3:双旋+变色

左右双旋+变色

右左双旋+变色

红黑树的删除:

5.源码


1.红黑树的概念

红黑树是一棵⼆叉搜索树,名字的"红""黑"是说,他的每个结点用⼀个存储位来表⽰结点的颜⾊,可以是红⾊或者⿊⾊

通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束,红⿊树确保没有⼀条路径会⽐其他路 径⻓出2倍,因⽽是接近平衡的

2.红黑树的规则

  1. 每个节点只能为红色或黑色
  2. 根节点必须为黑色
  3. 任意一个节点,从该节点到其所有的NULL节点的简单路径上,都包含相同数量的黑色节点
  4. 如果一个节点是红色的,它的两孩子个节点必须是黑色的

依据第四条规则,不难发现,红黑树中不能出现连续的红节点

在一些书上也会将"叶子节点"(走到空的位置)画出,方便标识所有的路径

依据红黑树的规则 可以保证从根节点出发,最长路径不超过最短路径的2倍

因为从黑色的根节点出发,每条路径上的黑色节点数量相同(规则3),而红色节点不可以连续出现(规则4),所以,最短 路径是全黑最长 路径是一黑一红

3.红黑树的效率

节点数量为N的红黑树,h为最短路径,则2h为最长路径,查找的次数为红黑树的高度次

N ,则 h

注:以一颗最低节点的满二叉树计算,总节点为

以一颗最高节点的满二叉树计算,总节点为

4.红黑树的实现

红黑树的节点

红黑树节点的颜色不是红色就是黑色,因此可以使用枚举描述其颜色

// 枚举值表⽰颜⾊

enum Colour

{

RED, BLACK

};

红黑树的每个节点需要包含最基本的左右指针,父节点的指针(方便后续的变色及旋转操作)

默认使用key/value结构实现

template<class K, class V>

struct RBTreeNode

{

// 这⾥更新控制平衡也要加⼊parent指针

pair<K, V> _kv;

RBTreeNode<K, V>* _left;

RBTreeNode<K, V>* _right;

RBTreeNode<K, V>* _parent;

Colour _col;

RBTreeNode(const pair<K, V>& kv)

:_kv(kv)

, _left(nullptr)

, _right(nullptr)

, _parent(nullptr)

{}

};

template<class K, class V>

class RBTree

{

typedef RBTreeNode<K, V> Node;

public:

private:

Node* _root = nullptr;

};

红黑树的插入

红黑树插入一个值的过程

  1. 插⼊⼀个值按⼆叉搜索树规则进⾏插⼊(左根右),插⼊后我们只需要观察是否符合红⿊树的4条规则
  2. 如果是空树插⼊,新增结点是⿊⾊结点。如果是⾮空树插⼊,新增结点必须红⾊结点,因为⾮空树 插⼊,新增⿊⾊结点就破坏了规则4,规则4是很难维护的
  3. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是⿊⾊的,则没有违反任何规则,插⼊结束
  4. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是红⾊的,则违反规则3。进⼀步分析,c是 红⾊,p为红,g必为⿊,这三个颜⾊都固定了,关键的变化看u的情况,需要根据u分为以下⼏种情况分别处理

下图中假设我们把新增结点标识为c(cur),c的⽗亲标识为p(parent),p的⽗亲标识为 g(grandgather),p的兄弟标识为u

情况1.变色,继续向上处理

c为红,p为红,g为⿊,u存在且为红,则将p和u变⿊,g变红

p是必须要变黑的,因为红色节点不能连续出现

为什么不把c变黑?因为c变黑和插入黑色节点无异,而黑色节点的插入会导致黑色节点的数量难以平衡

p与u这两条路径都经过了g节点,将g变红,p和u变黑,既确保了红色节点不连续出现,又保证了每条路径上的黑色节点的数量相同,符合红黑树的规则

cpp 复制代码
if (uncle && uncle->_col == RED)
{
	//u存在且为红,变色
	parent->_col = uncle->_col = BLACK;
	grand->_col = RED;

	cur = grand;
	parent = cur->_parent;
}

镜像的这种情况一样用这个方法处理,都是将p与u变黑,g变红

单纯改变颜色需要继续向上处理,因为改变了与祖先节点连接的节点的颜色(g节点由黑变红),可能会出现连续的红色节点,所以需要继续向上处理

情况2:单旋+变色

c为红,p为红,g为⿊,u不存在或者u存在且为⿊。u不存在,则c⼀定是新增结点,;u存在且为⿊则c一定不是新增,c之前是黑色的,是在c的子树中插入,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上来的

这里的单旋与AVL树的单旋几乎一模一样,具体可以参考我的这篇博客:

https://blog.csdn.net/2302_81258662/article/details/153416396?fromshare=blogdetail&sharetype=blogdetail&sharerId=153416396&sharerefer=PC&sharesource=2302_81258662&sharefrom=from_link

右单旋+变色
cpp 复制代码
//u存在且为黑,或不存在
//单旋+变色
if (cur == parent->_left)
{
	//  g
	// p u
	//c
	RotateR(grand);
	parent->_col = BLACK;
	grand->_col = RED;
}

这种情况下p一定要变黑,但是单纯变黑会导致黑色节点数量异常,为了保证黑色节点的数量符合规则,需要进行旋转,与之镜像的情况则进行相反方向的旋转,再变色处理

左单旋+变色
cpp 复制代码
//  g
// u p
//    c
RotateL(grand);
parent->_col = BLACK;
grand->_col = RED;

情况3:双旋+变色

c为红,p为红,g为⿊,u不存在或者u存在且为⿊,u不存在,则c⼀定是新增结点,u存在且为⿊,则 c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上 来的

分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解 决问题,需要旋转+变⾊

与AVL树中的双旋一样,这种带有折线的情况需要使用两次单旋来处理,即双旋

左右双旋+变色
cpp 复制代码
//  g
// p u
//  c
RotateL(parent);
RotateR(grand);
cur->_col = BLACK;
grand->_col = RED;
右左双旋+变色
cpp 复制代码
//  g
// u p
//  c
RotateR(parent);
RotateL(grand);
cur->_col = BLACK;
grand->_col = RED;

红黑树的删除:

红黑树的删除比较复杂,这里不做讲解,请参考其他文献

5.源码

包含红黑树的实现,遍历,以及测试,可直接运行

cpp 复制代码
#pragma once
#include<iostream>
#include <utility>
using namespace std;


// 枚举值表示颜色
enum Colour
{
	RED,
	BLACK
};

// 这里我们默认按key/value结构实现
template<class K, class V>
struct RBTreeNode
{
	// 这里更新控制平衡也要加入parent指针
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
	{}
};

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (nullptr == _root)
		{
			//根节点为空
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		else
		{
			//根节点不为空
			//寻找插入位置
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				if (kv.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (kv.first > cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					//插入的值冗余
					return false;
				}
			}
			//找到插入位置
			cur = new Node(kv);
			cur->_col = RED;
			//调整指针指向
			if (cur->_kv.first < parent->_kv.first)
			{
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}
			cur->_parent = parent;

			//向上调整
			while (parent && parent->_col == RED)
			{
				Node* grand = parent->_parent;
				if (parent == grand->_left)
				{
					Node* uncle = grand->_right;
					if (uncle && uncle->_col == RED)
					{
						//u存在且为红,变色
						parent->_col = uncle->_col = BLACK;
						grand->_col = RED;

						cur = grand;
						parent = cur->_parent;
					}
					else
					{
						//u存在且为黑,或不存在
						//单旋+变色
						if (cur == parent->_left)
						{
							//  g
							// p u
							//c
							RotateR(grand);
							parent->_col = BLACK;
							grand->_col = RED;
						}
						else
						{
							//  g
							// p u
							//  c
							RotateL(parent);
							RotateR(grand);
							cur->_col = BLACK;
							grand->_col = RED;
						}
						//经过旋转处理并没有将所连的旋转体的节点变色
						//无需向上处理
						break;
					}
				}
				else
				{
					Node* uncle = grand->_left;
					if (uncle && uncle->_col == RED)
					{
						//u存在且为红,变色
						parent->_col = uncle->_col = BLACK;
						grand->_col = RED;

						cur = grand;
						parent = cur->_parent;
					}
					else
					{
						//u不存在或存在且为黑
						if (cur == parent->_left)
						{
							//  g
							// u p
							//  c
							RotateR(parent);
							RotateL(grand);
							cur->_col = BLACK;
							grand->_col = RED;
						}
						else
						{
							//  g
							// u p
							//    c
							RotateL(grand);
							parent->_col = BLACK;
							grand->_col = RED;
						}
						//经过旋转处理并没有将所连的旋转体的节点变色
						//无需向上处理
						break;
					}
				}
			}
		}
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}


		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

	// 前序递归遍历
	bool Check(Node* root, int blackNum, const int refNum)
	{
		if (root == nullptr)
		{
			// 前序遍历走到空时,意味着一条路径走完了
			//cout << blackNum << endl;
			if (refNum != blackNum)
			{
				cout << "存在黑色结点的数量不相等的路径" << endl;
				return false;
			}
			return true;
		}

		// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了
		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "存在连续的红色结点" << endl;
			return false;
		}

		if (root->_col == BLACK)
		{
			blackNum++;
		}

		return Check(root->_left, blackNum, refNum)
			&& Check(root->_right, blackNum, refNum);
	}

	bool IsBalanceTree()
	{
		if (_root == nullptr)
			return true;

		if (_root->_col == RED)
			return false;

		// 参考值
		int refNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++refNum;
			}
			cur = cur->_left;
		}

		return Check(_root, 0, refNum);
	}

	void RotateR(Node* parent)
	{
		Node* pParent = parent->_parent;
		Node* NodeL = parent->_left;
		Node* NodeLR = NodeL->_right;

		//修正向下关系
		NodeL->_right = parent;
		parent->_left = NodeLR;

		//修正向上关系
		parent->_parent = NodeL;
		if (NodeLR)
		{
			NodeLR->_parent = parent;
		}
		//修正外部关系
		if (pParent)
		{
			NodeL->_parent = pParent;
			if (parent == pParent->_left)
			{
				pParent->_left = NodeL;
			}
			else
			{
				pParent->_right = NodeL;
			}
		}
		else
		{
			_root = NodeL;
			NodeL->_parent = nullptr;
		}
	}
	void RotateL(Node* parent)
	{
		Node* pParent = parent->_parent;
		Node* NodeR = parent->_right;
		Node* NodeRL = NodeR->_left;

		//修正向下关系
		parent->_right = NodeRL;
		NodeR->_left = parent;

		//修正向上关系
		parent->_parent = NodeR;
		if (NodeRL)
		{
			NodeRL->_parent = parent;
		}

		//修正外部关系
		if (pParent)
		{
			NodeR->_parent = pParent;
			if (parent == pParent->_left)
			{
				pParent->_left = NodeR;
			}
			else
			{
				pParent->_right = NodeR;
			}
		}
		else
		{
			_root = NodeR;
			NodeR->_parent = nullptr;
		}
	}
	void display()
	{
		_display(_root);
	}
	void _display(Node* root)
	{
		if (nullptr == root)
		{
			return;
		}
		_display(root->_left);
		cout << root->_kv.first << " ";
		_display(root->_right);
	}
private:
	Node* _root = nullptr;
};


void test()
{
	RBTree<int, int> rb;
	//变色
	//int arr[] = { 2,1,3,4 };
	//单旋
	//int arr[] = { 1,2,3 };
	//双旋
	//int arr[] = { 1,3,2 };
	pair<int, int> p1{ 2,1 }, p2{ 1,1 }, p3{ 3,1 }, p4{ 4,1 };

	rb.Insert(p2);
	rb.Insert(p3);
	rb.Insert(p1);
	//rb.Insert(p4);

	rb.display();
}

int main()
{
	test();
	return 0;
}
相关推荐
豐儀麟阁贵4 小时前
4.4数组的基本操作
java·开发语言·数据结构·算法
无限进步_4 小时前
【C语言】在矩阵中高效查找数字的算法解析
c语言·开发语言·数据结构·c++·其他·算法·矩阵
Yupureki4 小时前
从零开始的C++学习生活 11:二叉搜索树全面解析
c语言·数据结构·c++·学习·visual studio
草莓工作室4 小时前
数据结构2:线性表1-线性表类型及其特点
c语言·数据结构
再睡一夏就好5 小时前
【C++闯关笔记】STL:deque与priority_queue的学习和使用
java·数据结构·c++·笔记·学习·
遇印记5 小时前
网络运维学习笔记
数据结构·笔记·学习
_dindong6 小时前
牛客101:链表
数据结构·c++·笔记·学习·算法·链表
派大星爱吃猫6 小时前
堆的概念、结构与应用详解
c语言·数据结构·
aloha_78914 小时前
力扣hot100做题整理91-100
数据结构·算法·leetcode