【C++】红黑树原理与插入实现(附图解与源码)

红黑树

一、前言

二、什么是红黑树

1.概念

2.性质

三、构建一个红黑树

1.枚举两种颜色

2.定义红黑树节点

3.搭建红黑树

四、插入

1.查找插入位置

2.调整红黑树

插入时父节点为黑

插入时父节点为红

情况一:

情况二:

情况三:

五、源码


一、前言

        本文面向群体为刚接触红黑树的初学者群体,文章中构建的红黑树并不与STL中一致(因为对于初学者而言有很多点一下子解释不清),使用了较为简洁明了的方式解释红黑树的基本原理,方便各位萌新更好学习和理解。(编译环境为VS2019)

二、什么是红黑树

1.概念

        红黑树是二叉搜索树的一种,但它的每一个节点上都有着自己的颜色,也就是红色和黑色两种。通过对着色的限制,红黑树可以确保没有任何一条路径会比其他路径长两倍,也就是接近平衡的二叉搜索树(介于AVL和普通的树之间)。

2.性质

①每个结点不是红色就是黑色

根节点是黑色的 

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

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

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

三、构建一个红黑树

1.枚举两种颜色

// 枚举出红色和黑色两种颜色
// 作为红黑树节点的成员之一
enum colour
{
	BLACK,
	RED
};

2.定义红黑树节点

        此处我们使用pair类型为存储的数据类型,构建出的节点需要有五个成员,分别是颜色,父节点、左节点和右节点的地址,最后一个便是存储的数据类型。同时需要自己写一个构造函数。

template<class K, class V>
struct RBTreeNode
{
	
    // 需要将插入节点颜色默认为红色
    RBTreeNode(const pair<K, V> data)
		:_data(data)
		,_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_colour(RED)
	{}

	colour _colour;

	RBTreeNode<K, V>* _parent;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _left;

	pair<K, V> _data;
};

3.搭建红黑树

        再自定义一个类模板,将树的节点封装起来,这样一个树的雏形就形成了。

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
private:
	Node* _root = nullptr;
};

四、插入

1.查找插入位置

        要插入新的节点,就要先找到插入的位置,实现类似与find函数,二叉搜索树的查找原理还是很简单的:比当前节点存储的数据大的则往节点右边走,小的则往节点左边走,这样就保证了左节点都比父节点小,右节点都比父节点大了。具体代码实现如下:

bool Insert(const pair<K, V>& data)
{
	// 找到尾节点并插入新节点
	Node* parent = nullptr;
	Node* cur = _root;
	if (_root == nullptr) // 排除极端情况
	{
		_root = new Node(data);
		_root->_colour = BLACK;
		return true;
	}
	while (cur) // 找到
	{
		if (data.first > cur->_data.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (data.first < cur->_data.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else // 相等 不插入
			return false;
	}
	if (data.first < parent->_data.first) // 插入
	{
		cur = new Node(data);
		cur->_parent = parent;
		parent->_left = cur;
		parent = cur->_parent;
	}
	else if (data.first > parent->_data.first)
	{
		cur = new Node(data);
		cur->_parent = parent;
		parent->_right = cur;
		parent = cur->_parent;
	}
	else // 意料之外
		assert(false);
}

2.调整红黑树

        如果说红黑树比较难的话,那么对红黑树的调整绝对是最难的环节之一了,这一段如果不把逻辑捋顺、或者把具体的图画出来,那么到了细节处理时,我们面临的将是数不尽的bug呢(悲)

        如何调整?

        插入大体可以分为两种情况:

插入时父节点为黑

此时我们不需要对树进行调整,已经满足了红黑树的所有性质了,直接退出调整即可。

插入时父节点为红

这种情况最为复杂,我们需要将插入节点父亲的兄弟也要考虑进去,我们将它命名为叔叔节点,在这个基础上,我们可以再次细分为三种:

情况一.叔叔节点存在且为红,且新插入节点与 父节点和祖父节点的关系 一致;

情况二.叔叔节点不存在或者为黑,且新插入节点与 父节点和祖父节点的关系 一致;

情况三.叔叔节点不存在或者为黑,且新插入节点与 父节点和祖父节点的关系 相反。

情况一:

        我们需要让红黑树保持相对平衡,因此不能存在两个连续的红色节点,此处我们将父节点和叔叔节点变成黑色,将祖父节点变成红色,然后将当前节点指向祖父节点,将父节点更新(因为此时在树的最底层),将继续网上迭代更新。代码段实现如下:

// 调整红黑树
while (parent && parent->_colour == RED)
{
	// 获取祖父和叔叔节点(注:父节点为红,此时cur要么有祖父,要么没父亲)
	Node* grandparent = parent->_parent;
	Node* uncle;
	if (parent == grandparent->_left)
		uncle = grandparent->_right;
	else
		uncle = grandparent->_left;

	// 叔叔节点不存在或者为黑的情况
	if (!uncle || uncle->_colour == BLACK)
	{

	}
	else // 为红的情况
	{
		parent->_colour = uncle->_colour = BLACK;
		grandparent->_colour = RED;
		cur = grandparent;
	}
	parent = cur->_parent; // 注意更新父节点
}
// 根节点最终总是为黑
_root->_colour = BLACK
情况二:

        此时如果我们对情况二使用类似于情况一的解法,那么我们将会违背红黑树性质第四条,此时我们的解决方法是对红黑树节点进行单次旋转,旋转分为左旋和右旋,结果如图:

 

注:1.此处的nullptr不仅仅指nullptr,此处为了方便理解只列举出了这一种最容易理解的子树,其也可以是其他的子节点,需要将他们都一一对应起来;2.不同于AVL树的旋转,红黑树的旋转更多需要理解和实现的是颜色该如何变换。

代码段具体实现如下(如果想要真正掌握可以自己先动手尝试一下,细节处理尤为重要):

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

	// 变色
	parent->_colour = RED;
	cur->_colour = BLACK;

	parent->_right = cur->_left;
	if (curleft)
		curleft->_parent = parent;

	Node* pparent = parent->_parent;
	cur->_left = parent;
	parent->_parent = cur;

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

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

	// 变色
	parent->_colour = RED;
	cur->_colour = BLACK;

	parent->_left = cur->_right;
	if (curright)
		curright->_parent = parent;

	Node* pparent = parent->_parent;
	cur->_right = parent;
	parent->_parent = cur;

	if (pparent == nullptr)
	{
		_root = cur;
		cur->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
		{
			pparent->_left = cur;
		}
		else
		{
			pparent->_right = cur;
		}
		cur->_parent = pparent;
	}
}
情况三:

        第三种如果直接利用第二种情况来调整颜色的话,你会惊奇的发现,“啊!调整了!又好像没有调整...如调!”。直接调整会陷入死循环,这时候我们需要对树进行双旋,双旋其实也就是先对父节点进行单旋,再对祖父节点进行单旋,结果如图:

        此时不过是对cur和parent节点进行连续两次的旋转罢了,尽管可以复用已经完成的左右单旋,不过其中的细节较多,具体的实现需要各位通过代码的尝试才能一一试出来,双旋代码如图:

// 叔叔节点不存在或者为黑的情况
if (!uncle || uncle->_colour == BLACK)
{
	if (cur == parent->_left && parent == grandparent->_left)
	{
		RotateR(grandparent);
		cur = parent;
	}
	else if (cur == parent->_right && parent == grandparent->_right)
	{
		RotateL(grandparent);
		cur = parent;
	}
	else if (cur == parent->_right && parent == grandparent->_left)
	{
		RotateL(parent);
		RotateR(grandparent);
	}
	else if (cur == parent->_left && parent == grandparent->_right)
	{
		RotateR(parent);
		RotateL(grandparent);
	}
}
parent = cur->_parent; // 注意更新父节点

        以上就是全部的调整了,插入节点并完成了红黑树的调整,红黑树就能保持相对平衡的状态,各位可以自己测试一下(有测试代码放在源码部分)。

        希望本篇能对诸君有所帮助。 

五、源码

#pragma once

#include <iostream>
#include <assert.h>
using namespace std;

enum colour
{
	BLACK,
	RED
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V> data)
		:_data(data)
		,_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_colour(RED)
	{}

	colour _colour;

	RBTreeNode<K, V>* _parent;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _left;

	pair<K, V> _data;
};

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& data)
	{
		// 找到尾节点并插入新节点
		Node* parent = nullptr;
		Node* cur = _root;
		if (_root == nullptr) // 排除极端情况
		{
			_root = new Node(data);
			_root->_colour = BLACK;
			return true;
		}
		while (cur) // 找到
		{
			if (data.first > cur->_data.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (data.first < cur->_data.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else // 相等 不插入
				return false;
		}
		if (data.first < parent->_data.first) // 插入
		{
			cur = new Node(data);
			cur->_parent = parent;
			parent->_left = cur;
			parent = cur->_parent;
		}
		else if(data.first > parent->_data.first)
		{
			cur = new Node(data);
			cur->_parent = parent;
			parent->_right = cur;
			parent = cur->_parent;
		}
		else // 意料之外
			assert(false);

		// 调整红黑树
		while (parent && parent->_colour == RED)
		{
			// 获取祖父和叔叔节点(注:父节点为红,此时cur要么有祖父,要么没父亲)
			Node* grandparent = parent->_parent;
			Node* uncle;
			if (parent == grandparent->_left)
				uncle = grandparent->_right;
			else
				uncle = grandparent->_left;

			// 叔叔节点不存在或者为黑的情况
			if (!uncle || uncle->_colour == BLACK)
			{
				if (cur == parent->_left && parent == grandparent->_left)
				{
					RotateR(grandparent);
					cur = parent;
				}
				else if (cur == parent->_right && parent == grandparent->_right)
				{
					RotateL(grandparent);
					cur = parent;
				}
				else if (cur == parent->_right && parent == grandparent->_left)
				{
					RotateL(parent);
					RotateR(grandparent);
				}
				else if (cur == parent->_left && parent == grandparent->_right)
				{
					RotateR(parent);
					RotateL(grandparent);
				}
			}
			else // 为红的情况
			{
				parent->_colour = uncle->_colour = BLACK;
				grandparent->_colour = RED;
				cur = grandparent;
			}
			parent = cur->_parent; // 注意更新父节点
		}
		_root->_colour = BLACK; // 根节点始终为黑
	}

	// 检测
	bool IsValidRBTree()
	{
		Node* pRoot = _root;
		// 空树也是红黑树
		if (nullptr == pRoot)
			return true;

		// 检测根节点是否满足情况
		if (BLACK != pRoot->_colour)
		{
			cout << "违反红黑树性质二:根节点必须为黑色" << endl;
			return false;
		}

		// 获取任意一条路径中黑色节点的个数
		size_t blackCount = 0;
		Node* pCur = pRoot;
		while (pCur)
		{
			if (BLACK == pCur->_colour)
				blackCount++;
			pCur = pCur->_left;
		}

		// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
		size_t k = 0;
		return _IsValidRBTree(pRoot, k, blackCount);
	}

private:
	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->_colour)
			k++;

		// 检测当前节点与其双亲是否都为红色
		Node* pParent = pRoot->_parent;
		if (pParent && RED == pParent->_colour && RED == pRoot->_colour)
		{
			cout << "违反性质三:没有连在一起的红色节点" << endl;
			return false;
		}
		return _IsValidRBTree(pRoot->_left, k, blackCount) &&
			_IsValidRBTree(pRoot->_right, k, blackCount);
	}

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

		// 变色
		parent->_colour = RED;
		cur->_colour = BLACK;

		parent->_right = cur->_left;
		if (curleft)
			curleft->_parent = parent;

		Node* pparent = parent->_parent;
		cur->_left = parent;
		parent->_parent = cur;

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

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

		// 变色
		parent->_colour = RED;
		cur->_colour = BLACK;

		parent->_left = cur->_right;
		if (curright)
			curright->_parent = parent;

		Node* pparent = parent->_parent;
		cur->_right = parent;
		parent->_parent = cur;

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

	Node* _root = nullptr;
};

猜你喜欢

转载自blog.csdn.net/qq_74641564/article/details/132910811