数据结构——红黑树

目录

一.红黑树

二.红黑树的实现

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;
}
相关推荐
Biomamba生信基地几秒前
R语言基础| 回归分析
开发语言·回归·r语言
黑客-雨14 分钟前
从零开始:如何用Python训练一个AI模型(超详细教程)非常详细收藏我这一篇就够了!
开发语言·人工智能·python·大模型·ai产品经理·大模型学习·大模型入门
Pandaconda19 分钟前
【Golang 面试题】每日 3 题(三十九)
开发语言·经验分享·笔记·后端·面试·golang·go
半盏茶香20 分钟前
扬帆数据结构算法之雅舟航程,漫步C++幽谷——LeetCode刷题之移除链表元素、反转链表、找中间节点、合并有序链表、链表的回文结构
数据结构·c++·算法
加油,旭杏23 分钟前
【go语言】变量和常量
服务器·开发语言·golang
行路见知23 分钟前
3.3 Go 返回值详解
开发语言·golang
xcLeigh27 分钟前
WPF实战案例 | C# WPF实现大学选课系统
开发语言·c#·wpf
哎呦,帅小伙哦28 分钟前
Effective C++ 规则41:了解隐式接口和编译期多态
c++·effective c++
NoneCoder37 分钟前
JavaScript系列(38)-- WebRTC技术详解
开发语言·javascript·webrtc
关关钧1 小时前
【R语言】数学运算
开发语言·r语言