数据结构——红黑树

目录

一.红黑树

二.红黑树的实现

1.红黑树节点的定义

2.红黑树的插入

3.红黑树的遍历

4.检测红黑树

5.红黑树的查找

6.红黑树的性能

三.整体代码

1.RBTree.h

2.RBTree.cpp


一.红黑树

1.红黑树的概念

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

2.红黑树的性质

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

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

因为最短路径为全黑,最长路径为1红1黑,路径中的黑色节点数相同,中间可以插入红色节点

二.红黑树的实现

1.红黑树节点的定义

cpp 复制代码
enum Colour
{
	BLACK,
	RED,
};

template<class K,class V>
struct RBTreeNode
{
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;

	pair<K, V> _kv;

	Colour _col;

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

2.红黑树的插入

首先按照二叉搜索树的规则进行插入

cpp 复制代码
//按照搜索树的规则插入
if (_root == nullptr)
{
	_root = new Node(kv);
	_root->_col = BLACK;
	return true;
}

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);
if (parent->_kv.first < kv.first)
{
	parent->_right = cur;
	cur->_parent = parent;
}
else
{
	parent->_left = cur;
	cur->_parent = parent;
}

//新增节点红的
cur->_col = RED;

这里为什么将新增节点的颜色设置为红的?

新增节点颜色的选择主要看的是对性质的破坏程度,选择红的,会破坏不能有连续的红色节点性质,而选择黑的,因为每条路径的黑色节点数需要数目相同,设为黑的每一条都需要改变,相比较之下,破坏不能有连续红色节点的性质较为轻

然后检测新节点插入后,红黑树的性质是否造到破坏

因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何 性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:

cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

情况一: cur为红,p为红,g为黑,u存在且为红

解决方式:将p,u改为黑,g改为红

注意:如果g为根,调整完后将g变为黑色

如果g是子树,g一定有双亲,如果为红色,继续向上调整

cpp 复制代码
	//情况一:uncle存在且为红
	if (uncle && uncle->_col == RED)
	{
		parent->_col = uncle->_col = BLACK;
		grandfather->_col = RED;

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

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

u的情况有两种:

  1. 如果u不存在,则cur一定为新增节点,因为如果cur不是新增的,那么cur和p一定有一个为黑的,不满足每条路径上黑色节点数目相同的性质
  2. 如果u存在,为黑色,那么cur原来的颜色一定为黑色,现在是红的是因为cur的子树调整过程中将cur的颜色由黑色变为红色

如何调整?

  1. p为g的左孩子,cur为p的左孩子,进行右单旋
  2. p为g的右孩子,cur为p的右孩子,进行左单旋
  3. 然后将p变黑,g变红

红黑树的左单旋,右单旋与AVL树的一样,只是没有平衡因子的调整,在这里直接给出

cpp 复制代码
	//左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		subR->_left = parent;
		Node* ppNode = parent->_parent;
		parent->_parent = subR;

		//原来parent为根,现在subR为根
		//parent为树的子树,sunR替代parent
		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;

			subR->_parent = ppNode;
		}
	}

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

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		subL->_right = parent;
		Node* ppNode = parent->_parent;
		parent->_parent = subL;

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

			subL->_parent = ppNode;
		}
	}

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

如何调整?

  1. p为g的左孩子,cur为p的右孩子,对p进行左单旋,转化为情况二,再对g进行右单旋
  2. p为g的右孩子,cur为p的左孩子,对p进行右单旋,转化为情况二,再对g进行左单旋

三种情况的插入实现如下

cpp 复制代码
	bool Insert(const pair<K, V>& kv)
	{
		//按照搜索树的规则插入
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

		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);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//新增节点红的
		cur->_col = RED;
		while (parent && parent->_col == RED)
		{
			//红黑树的关键看叔叔
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//情况一:uncle存在且为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二或情况三:uncle不存在或者uncle存在且为黑
				else
				{
					//情况三:双旋->变为单旋
					if (cur == parent->_right)
					{
						RotateL(parent);
						swap(parent, cur);
					}
					//第二种情况(有可能为第三种情况变化而来)
					RotateR(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;

					break;
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				//情况一:uncle存在且为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二或情况三:uncle不存在或者uncle存在且为黑
				else
				{
					//情况三:双旋->变为单旋
					if (cur == parent->_left)
					{
						RotateR(parent);
						swap(parent, cur);
					}
					//第二种情况(有可能为第三种情况变化而来)
					RotateL(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;

					break;
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}

3.红黑树的遍历

cpp 复制代码
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

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

	void InOrder()
	{
		_InOrder(_root);
	}

4.检测红黑树

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

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

5.红黑树的查找

红黑树的查找与搜索树相同,大的向右找,小的向左找

cpp 复制代码
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

6.红黑树的性能

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

红黑树的性能与AVL树差了基本两倍,但是我们认为他们基本相同,为什么?

因为现在硬件的运算速度非常快,之间基本没有差异,log2N和2*log2N差别不大了

可以通过以下代码测试性能

cpp 复制代码
void Testtime()
{
	const int n = 1000000;
	vector<int> v;
	v.reserve(n);
	srand(time(0));
	for (size_t i = 0; i < n; ++i)
	{
		v.push_back(rand());
	}

	RBTree<int, int> rbtree;
	size_t begin1 = clock();
	for (auto e : v)
	{
		rbtree.Insert(make_pair(e, e));
	}
	size_t end1 = clock();

	cout << end1 - begin1 << endl;
}
cpp 复制代码
void Testtime()
{
	const int n = 1000000;
	vector<int> v;
	v.reserve(n);
	srand(time(0));
	for (size_t i = 0; i < n; ++i)
	{
		v.push_back(rand());
	}

	AVLTree<int, int> avltree;
	size_t begin1 = clock();
	for (auto e : v)
	{
		avltree.Insert(make_pair(e, e));
	}
	size_t end1 = clock();

	cout << end1 - begin1 << endl;
}

可以看到相同的100w个数据,红黑树189,AVL树176,他们之间差距并不是很大

三.整体代码

1.RBTree.h

cpp 复制代码
#pragma once

enum Colour
{
	BLACK,
	RED,
};

template<class K,class V>
struct RBTreeNode
{
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;

	pair<K, V> _kv;

	Colour _col;

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

template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		//按照搜索树的规则插入
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

		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);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//新增节点红的
		cur->_col = RED;
		while (parent && parent->_col == RED)
		{
			//红黑树的关键看叔叔
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//情况一:uncle存在且为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二或情况三:uncle不存在或者uncle存在且为黑
				else
				{
					//情况三:双旋->变为单旋
					if (cur == parent->_right)
					{
						RotateL(parent);
						swap(parent, cur);
					}
					//第二种情况(有可能为第三种情况变化而来)
					RotateR(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;

					break;
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				//情况一:uncle存在且为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二或情况三:uncle不存在或者uncle存在且为黑
				else
				{
					//情况三:双旋->变为单旋
					if (cur == parent->_left)
					{
						RotateR(parent);
						swap(parent, cur);
					}
					//第二种情况(有可能为第三种情况变化而来)
					RotateL(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;

					break;
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}

	//左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		subR->_left = parent;
		Node* ppNode = parent->_parent;
		parent->_parent = subR;

		//原来parent为根,现在subR为根
		//parent为树的子树,sunR替代parent
		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;

			subR->_parent = ppNode;
		}
	}

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

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		subL->_right = parent;
		Node* ppNode = parent->_parent;
		parent->_parent = subL;

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

			subL->_parent = ppNode;
		}
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

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

	void InOrder()
	{
		_InOrder(_root);
	}

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

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

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
private:
	Node* _root = nullptr;
};

void TestRBTree()
{
	int a[] = { 4,5,7,8,1,2,3,9,10 };
	RBTree<int, int> r;
	for (auto e : a)
	{
		r.Insert(make_pair(e, e));
	}
	r.InOrder();
	cout << r.IsValidRBTree() << endl;
}

void Testtime()
{
	const int n = 1000000;
	vector<int> v;
	v.reserve(n);
	srand(time(0));
	for (size_t i = 0; i < n; ++i)
	{
		v.push_back(rand());
	}

	RBTree<int, int> rbtree;
	size_t begin1 = clock();
	for (auto e : v)
	{
		rbtree.Insert(make_pair(e, e));
	}
	size_t end1 = clock();

	cout << end1 - begin1 << endl;
}

2.RBTree.cpp

cpp 复制代码
#include<iostream>
#include<vector>
#include<time.h>
using namespace std;
#include"RBTree.h"

int main()
{
	TestRBTree();
	Testtime();
	return 0;
}
相关推荐
zhangpz_31 分钟前
c ++零基础可视化——数组
c++·算法
UestcXiye34 分钟前
《TCP/IP网络编程》学习笔记 | Chapter 12:I/O 复用
c++·网络协议·计算机网络·ip·tcp
fcopy37 分钟前
Golang云原生项目:—实现ping操作
开发语言·golang
Clown9541 分钟前
go-zero(二) api语法和goctl应用
开发语言·后端·golang
Python私教41 分钟前
go语言中的占位符有哪些
开发语言·后端·golang
__基本操作__1 小时前
OPENCV 检测直线[opencv--3]
c++·计算机视觉
誓约酱1 小时前
Linux 下网络套接字(Socket) 与udp和tcp 相关接口
linux·网络·c++·tcp/ip·udp
007php0071 小时前
使用 Go 实现将任何网页转化为 PDF
开发语言·后端·python·docker·chatgpt·golang·pdf
阿华的代码王国1 小时前
【Bug合集】——Java大小写引起传参失败,获取值为null的解决方案
java·开发语言·springboot
阑梦清川2 小时前
概率论之常见分布与matlab绘图
开发语言·matlab·概率论