C++模拟实现红黑树

目录

1.文章概述

2.红黑树介绍

2.1.红黑树概念

2.2红黑树的性质

2.3红黑树和AVL树的对比

3.红黑树插入和查找实现

3.1红黑树插入

3.2红黑树查找

4.所有代码

5.总结


1.文章概述

本文适合熟悉二叉平衡树和AVL树的人阅读,因为红黑树是在这两者的基础上优化而来的,是一个更加高效的数据结构,里面的许多逻辑基础是与平衡二叉树或者AVL树一样的。

本文会阐述红黑树的实现逻辑并且用c++代码将红黑树增查改的功能用代码实现出来,帮助我们更好地理解红黑树,并且提高理解和编程能力。

2.红黑树介绍

2.1.红黑树概念

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

2.2红黑树的性质

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

2.根结点是黑色的;

3.如果一个结点是红色的,则它的两个孩子结点必须是黑色的,即任何路径上没有连续的红色结点;

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

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

用两个特例解释:

这两颗树都是红黑树,最长的路径刚好是最短路径的两倍。

2.3红黑树和AVL树的对比

|------------|----------------------|
| AVL树 | 红黑树 |
| 高度差不超过1 | 最长路径不超过最短路径的2倍 |
| 存1000个值 | 存1000个值 |
| logN == 10 | 考虑极端情况:2*logN == 20 |
| 10亿个值 | 10亿个值 |
| logN == 30 | 2*logN == 60 |
| 相当于1毛钱 | 相当于2毛钱 |
[红黑树和AVL树的对比]

综上,性能是同一量级的。但是AVL树,控制严格平衡是付出代价。插入和删除时需要进行大量的旋转。

3.红黑树插入和查找实现

3.1红黑树插入

首先如同二叉平衡树一样,根据每个结点右边大,左边小的性质,先找到插入的位置。

为了简化,我们这里限制不能插入相同的值。

cpp 复制代码
		Node* cur = _pHead->_pLeft;
		Node* parent = _pHead;
		while (cur)
		{
			parent = cur;
			if (data == cur->data)
				return false;
			else if (data > cur->data)
				cur = cur->_pRight;
			else
				cur = cur->_pLeft;
		}
		if (data > parent->data)
			parent->_pRight = newNode;
		else
			parent->_pLeft = newNode;
		newNode->_pParent = parent;
		cur = newNode;

插入后,如果新结点的父结点是黑色的,那么就插入结束。(默认插入新结点是红色的)

否则,将会进入红黑树插入中较难的环节。

找到cur的父结点,爷爷结点,叔叔结点。

cpp 复制代码
		Node* grandParent = parent->_pParent;
		Node* uncle = nullptr;
		if (parent == grandParent->_pLeft)
			uncle = grandParent->_pRight;
		else
			uncle = grandParent->_pLeft;

接着,我们根据叔叔结点的三种情况来对新插入结点做处理,以使得新插入后任然保持红黑树的性质。

1.如果叔叔存在且叔叔是红色的,那么需要变色和继续向上更新:

将叔叔和爸爸结点变成黑色,祖先结点变成红色;如果祖先结点的父结点是黑的,那么结束,如果是红色的,那么继续向上更新,将现在的祖先结点当做当前结点(cur)。

2.如果叔叔不存在或者叔叔存在且是黑色的,那么只需要旋转之后再变色:

四种旋转与AVL树一样,分别书左单旋,右单旋,左右和右左旋转,这里不做详细说明。变色则需要将当前结点变成黑色,父结点和祖父结点变成红色。

代码展示:

cpp 复制代码
while (uncle && uncle->color == RED)
{
	uncle->color = parent->color = BLACK;
	grandParent->color = RED;
	cur = grandParent;
	parent = cur->_pParent;
	if (parent == _pHead)
	{
		cur->color = BLACK;
		return true;
	}
	else if (parent->color == BLACK)
		return true;
	else if (parent->color == RED)
	{
		grandParent = parent->_pParent;
		if (parent == grandParent->_pLeft)
			uncle = grandParent->_pRight;
		else
			uncle = grandParent->_pLeft;
	}
	else
		assert("未知错误");
}
if (uncle == nullptr || (uncle && uncle->color == BLACK))
{
	if (grandParent->_pRight == parent && parent->_pRight == cur)
		RotateL(grandParent);
	else if(grandParent->_pLeft == parent && parent->_pLeft == cur)
		RotateR(grandParent);
	else if (grandParent->_pRight == parent && parent->_pLeft == cur)
	{
		RotateR(parent);
		RotateL(grandParent);
	}
	else if (grandParent->_pLeft == parent && parent->_pRight == cur)
	{
		RotateL(parent);
		RotateR(grandParent);
	}
	grandParent->color = RED;
	parent->color = RED;
	cur->color = BLACK;
	if (_pHead->_pLeft != _pHead->_pRight)
		_pHead->_pLeft = _pHead->_pRight = cur;
	return true;
}

注意:_pHead是哨兵位的头结点,旋转时会将它的左指针和右指针指向不同的结点,这是不对的,旋转后应该都指向cur,我们在代码最后进行了一次检查。

至此,红黑树的插入就完成了。

3.2红黑树查找

查找类似二叉平衡树,代码如下:

cpp 复制代码
	// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
	Node* Find(const T& data)
	{
		Node* cur = _pHead->_pLeft;
		while (cur)
		{
			if (cur->data == data)
				return cur;
			else if (data > cur->data)
				cur = cur->_pRight;
			else  
				cur = cur->_pLeft;
		}
		return nullptr;
	}

至此,插入和查找都完成了,修改部分会在下一篇封装部分展现,删除部分可在《算法导论》中进行查看。

4.所有代码

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

enum color
{
	RED,
	BLACK
};

template <class T>
struct RBTreeNode
{
	T data;
	RBTreeNode* _pParent;
	RBTreeNode* _pLeft;
	RBTreeNode* _pRight;
	color color;
	RBTreeNode(T val = T())
	{
		data = val;
		_pParent = nullptr;
		_pLeft = nullptr;
		_pRight = nullptr;
		color = RED;
	}
};


// 请模拟实现红黑树的插入--注意:为了后序封装map和set,本文在实现时给红黑树多增加了一个头结点
template <class T>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	RBTree()
	{
		_pHead = new Node;
		_pHead->_pLeft = _pHead;
		_pHead->_pRight = _pHead;
	}

	// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
	// 注意:为了简单起见,本次实现红黑树不存储重复性元素
	bool Insert(const T& data)
	{
		//插入第一个节点
		Node* newNode = new Node(data);
		if (_pHead->_pLeft == _pHead)
		{
			_pHead->_pLeft = newNode;
			_pHead->_pRight = newNode;
			newNode->_pParent = _pHead;
			newNode->color = BLACK;
			return true;
		}

		//不是插入第一个节点
		Node* cur = _pHead->_pLeft;
		Node* parent = _pHead;
		while (cur)
		{
			parent = cur;
			if (data == cur->data)
				return false;
			else if (data > cur->data)
				cur = cur->_pRight;
			else
				cur = cur->_pLeft;
		}
		if (data > parent->data)
			parent->_pRight = newNode;
		else
			parent->_pLeft = newNode;
		newNode->_pParent = parent;
		cur = newNode;
		
		//连上了
		if (parent->color == BLACK)
			return true;

		Node* grandParent = parent->_pParent;
		Node* uncle = nullptr;
		if (parent == grandParent->_pLeft)
			uncle = grandParent->_pRight;
		else
			uncle = grandParent->_pLeft;

		while (uncle && uncle->color == RED)
		{
			uncle->color = parent->color = BLACK;
			grandParent->color = RED;
			cur = grandParent;
			parent = cur->_pParent;
			if (parent == _pHead)
			{
				cur->color = BLACK;
				return true;
			}
			else if (parent->color == BLACK)
				return true;
			else if (parent->color == RED)
			{
				grandParent = parent->_pParent;
				if (parent == grandParent->_pLeft)
					uncle = grandParent->_pRight;
				else
					uncle = grandParent->_pLeft;
			}
			else
				assert("未知错误");
		}
		if (uncle == nullptr || (uncle && uncle->color == BLACK))
		{
			if (grandParent->_pRight == parent && parent->_pRight == cur)
				RotateL(grandParent);
			else if(grandParent->_pLeft == parent && parent->_pLeft == cur)
				RotateR(grandParent);
			else if (grandParent->_pRight == parent && parent->_pLeft == cur)
			{
				RotateR(parent);
				RotateL(grandParent);
			}
			else if (grandParent->_pLeft == parent && parent->_pRight == cur)
			{
				RotateL(parent);
				RotateR(grandParent);
			}
			grandParent->color = RED;
			parent->color = RED;
			cur->color = BLACK;
			if (_pHead->_pLeft != _pHead->_pRight)
				_pHead->_pLeft = _pHead->_pRight = cur;
			return true;
		}
		return false;
	}

	// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
	Node* Find(const T& data)
	{
		Node* cur = _pHead->_pLeft;
		while (cur)
		{
			if (cur->data == data)
				return cur;
			else if (data > cur->data)
				cur = cur->_pRight;
			else  
				cur = cur->_pLeft;
		}
		return nullptr;
	}

	// 获取红黑树最左侧节点
	Node* LeftMost()
	{
		Node* cur = _pHead;
		while (cur->_pLeft)
		{
			cur = cur->_pLeft;
		}
		return cur;
	}

	// 获取红黑树最右侧节点
	Node* RightMost()
	{
		Node* cur = _pHead;
		while (cur->_pRight)
		{
			cur = cur->_pRight;
		}
		return cur;
	}

	// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
	bool IsValidRBTRee()
	{
		if (_pHead->_pLeft == nullptr)
			return true;
		else if (_pHead->_pLeft->color == RED)
			return false;
		return _IsValidRBTRee(_pHead->_pLeft, 0, pathBlack());
	}
private:
	int pathBlack()
	{
		Node* cur = _pHead->_pLeft;
		int ret = 0;
		while (cur)
		{
			if (cur->color == BLACK)
				ret++;
			cur = cur->_pLeft;
		}
		return ret;
	}
	bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack)
	{
		if (pRoot == nullptr)
		{
			if (blackCount != pathBlack)
				return false;
			return true;
		}
		if (pRoot->color == BLACK)
			blackCount++;
		if (pRoot->color == RED && pRoot->_pParent->color == RED)
			return false;
		return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack) &&
			_IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);
	}
	// 左单旋
	void RotateL(Node* pParent)
	{
		Node* cur = pParent->_pRight;
		Node* curLeft = cur->_pLeft;
		Node* parentP = pParent->_pParent;

		pParent->_pRight = curLeft;
		if (curLeft)
			curLeft->_pParent = pParent;
		cur->_pLeft = pParent;
		pParent->_pParent = cur;

		cur->_pParent = parentP;
		if (parentP->_pLeft == pParent)
			parentP->_pLeft = cur;
		else
			parentP->_pRight = cur;
	}
	// 右单旋
	void RotateR(Node* pParent)
	{
		Node* cur = pParent->_pLeft;
		Node* curRight = cur->_pRight;
		Node* parentP = pParent->_pParent;

		pParent->_pLeft = curRight;
		if (curRight)
			curRight->_pParent = pParent;
		cur->_pRight = pParent;
		pParent->_pParent = cur;

		cur->_pParent = parentP;
		if (parentP->_pLeft == pParent)
			parentP->_pLeft = cur;
		else
			parentP->_pRight = cur;
	}
	// 为了操作树简单起见:获取根节点
	Node*& GetRoot()
	{
		return _pHead;
	}
private:
	Node* _pHead;
};

5.总结

红黑树的实现在理解了平衡二叉树和实现过AVL树的基础上其实也不难,手搓红黑树拿捏。

相关推荐
HL_LOVE_C1 分钟前
全面理解-c++11中的智能指针
开发语言·c++
亲爱的老吉先森6 分钟前
常见数据结构的C语言定义---《数据结构C语言版》
c语言·开发语言·数据结构
KAI773818 分钟前
2月10日QT
c++
獨枭2 小时前
在 MFC 应用中集成 WebView2
c++·mfc
清泓y3 小时前
UE5--浅析委托原理(Delegate)
c++·ue5·ue4
RangoLei_Lzs3 小时前
C++性能优化—AI润色版
开发语言·c++·性能优化
egoist20234 小时前
【C++】命名空间
开发语言·c++·命名空间
敲上瘾4 小时前
DFS+回溯+剪枝(深度优先搜索)——搜索算法
数据结构·c++·算法·回归·深度优先·剪枝·回归算法
獨枭5 小时前
MFC 应用最小化到系统托盘
c++·mfc
纪伊路上盛名在5 小时前
scRNA-seq scanpy教程1:准备工作+AnnData数据结构理解
数据结构·python·可视化·生物学·单细胞·scanpy