【C++】红黑树的模拟实现

目录

一、红黑树的概念

1、什么是红黑树

2、红黑树的性质

二、红黑树的模拟实现

1、节点的定义

 2、红黑树的插入操作

3、变色+向上处理

4、变色+旋转

5、红黑树的查找

6、红黑树的析构

三、红黑树的验证

1、是否满足搜索二叉树 

​ 2、是否满足红黑树的性质

四、红黑树和AVL数的比较

五、完整代码


一、红黑树的概念

1、什么是红黑树

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

2、红黑树的性质

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

2. 根节点是黑色的 

3. 如果一个节点是红色的,则它的两个孩子结点是黑色的。【不能出现连续的红色节点

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

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

二、红黑树的模拟实现

1、节点的定义

跟AVL树节点差不多,但是需要多定义一个颜色。可以用枚举来定义。

其中,在初始化列表中,默认的节点颜色是红色。是因为当我们新增节点时,如果新增一个黑色节点,那每一条路径都需要更改黑色节点的数量才能满足红黑树的要求,为了减少工作量,所以将默认颜色设置为红色。

 2、红黑树的插入操作

红黑树的插入其实就是在搜索树的插入基础上加上了变色处理。所以前面的插入操作和搜索树是一样的。

3、变色+向上处理

我们将新增节点称作 cur,父节点为 parent,父节点的父亲称作 grandfather,grandfather 的另一个子节点称作 uncle

红黑树的颜色调整的重点在与 uncle 节点,接下来我们也是根据 uncle 节点的情况来分析。

【当uncle 节点存在且为红色时】 

我们新增的是一个红色的节点,但是这样就会违反红黑树的规则3(不能出现连续的红节点),所以我们将父节点和叔节点的颜色变成黑色。但是这样又会出现一个新的问题,违反了规则4(每条路径都包含相同数目的黑色结点 )。此时我们将grandfather 的节点颜色变成红节点,这样黑色节点的数量就一样多了。

这时又要分情况讨论:

1.如果祖父没有父亲节点,说明祖父是根节点,根节点必须是黑色节点,所以我们只需要将祖父的颜色变成黑色即可。

2.如果祖父有父亲节点且节点颜色是黑色时,就不用调整了。

3.如果祖父有父亲节点且节点颜色是红色时,就将祖父节点变成 cur,祖父的父亲节点变成 parent,再次重复刚刚的操作即可。

4、变色+旋转

【当 uncle 节点不存在时】

新增的是一个红色节点,此时将父节点的颜色变成红色是不能解决问题的。因为没有uncle节点,然后又新增了一个节点会导致高度差大于1,所以此时我们可以借助AVL数旋转的思想, 来降低子树的高度。将 grandparent 变成 parent 的右节点,将 parent 节点往上移,然后将grandparent节点的颜色变成红色,将 parent 节点的颜色变成黑色。

当左边高就右旋,当右边高就左旋。

【当 uncle 节点为黑色时】

 1.当uncle存在且为黑色时,单纯的变色也解决不了问题,也需要使用旋转来处理,处理完后再进行变色。
2.不过这种情况一般都是从下面变色上来遇到的,所以可能是不断的往上变色处理然后遇到了这种情况,无法处理就需要使用旋转了。而旋转是通过图形来确定的。

我们会发现 uncle 不存在或者uncle存在且为黑色时,需要变色+旋转,所以在写代码时我们可以将他合成一种。 在哪边插入,哪边的高度就会上升,我们使用旋转使高度下降。

因为一开始不知道叔叔是在那边,叔叔在哪侧位置会决定旋转的方向(本质上是父亲在哪边决定旋转方向),所以需要讨论一下,当父亲在左边,那么叔叔就在右边。父亲在左边那么进行的的就是右旋和双旋,如果父亲在右边,那么进行的就是左旋和双旋

【总结】

红黑树节点的变色与旋转的关键在于 uncle 节点

当uncle存在且为黑色时,需要进行旋转+变色处理。
当uncle不存在时,需要进行旋转+变色处理。
当uncle存在且为红色时,需要进行变色+向上处理。
 

5、红黑树的查找

从根开始查找,如果大于就往右边找;如果小于就往左边找;等于就找到了。如果没有找到直接返回 false。 

6、红黑树的析构

如果这棵树是空树,直接返回。然后用后序,递归依次销毁。

三、红黑树的验证

 红黑树的检测分为两步:

1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)

2. 检测其是否满足红黑树的性质

1、是否满足搜索二叉树 

 2、是否满足红黑树的性质

 检验是否为红黑树,我们按照红黑树的性质一条一条的来检查:

解决思路: 

1、如果根节点存在且为红色,那就不是红黑树

2、不能有连续的红节点----如果是红节点,那么它的父节点就不能是红节点。

3、可以算出一条路径上黑色节点的数量,然后跟其他路径的黑色节点数量相比较,如果出现不相等就不符合红黑树。 

【测试代码】

四、红黑树和AVL数的比较

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

五、完整代码

#pragma once

//用枚举来定义每一个节点的颜色
enum Colour
{
	RED,
	BLACK,
};


template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _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:

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

		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}

	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);
		//插入的是红色节点
		cur->_col = RED;
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		//接下来就是颜色的调整
		while (parent && parent->_col == RED)
		{
			Node* grandparent = parent->_parent;
			if (grandparent->_left == parent)
			{
				Node* uncle = grandparent->_right;
				//情况一:u存在且为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandparent->_col = RED;
					//继续往上调整
					cur = grandparent;
					parent = cur->_parent;
				}
				else//u不存在/u存在且为空
				{
					//往左边插入,左边的高度会变高
					//     g
					//   p   u
					// c 
					if (cur == parent->_left)
					{
						RotateR(grandparent);
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					else
					{
						//往右边插入
						//     g
						//   p   u
						//     c
						RotateL(parent);
						RotateR(grandparent);
						cur->_col = BLACK;
						parent->_col = RED;
						grandparent->_col = RED;
					}
					break;
				}
			}
			else//grandparent->right==parent
			{
				//    g
				//  u   p
				//        c
				Node* uncle = grandparent->_left;
				//
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandparent->_col = RED;

					// 继续往上调整
					cur = grandparent;
					parent = cur->_parent;
				}
				else // u不存在/u存在且为黑,旋转+变色
				{
					//    g
					//  u   p
					//        c
					if (cur == parent->_right)
					{
						RotateL(grandparent);
						grandparent->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						//    g
						//  u   p
						//    c
						RotateR(parent);
						RotateL(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}

					break;
				}
			}
		}
		//不管怎样根节点都要保持黑色
		_root->_col = BLACK;

		return true;
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

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

	//判断是否是红黑树
	bool IsBalance()
	{
		if (_root && _root->_col == RED)
		{
			cout << "根节点颜色是红色" << endl;
			return false;
		}

		int benchmark = 0;
		//计算某一条路径的黑色节点
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				++benchmark;
			cur = cur->_left;
		}

		// 连续红色节点
		return _Check(_root, 0, benchmark);
	}

	bool _Check(Node* root, int blackNum, int benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark != blackNum)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}

			return true;
		}

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

		//判断是否有连续的红色节点
		if (root->_col == RED
			&& root->_parent
			&& root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		return _Check(root->_left, blackNum, benchmark)
			&& _Check(root->_right, blackNum, benchmark);
	}


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

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

		Node* ppnode = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (ppnode == nullptr)
		{
			_root = subR;
			_root->_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;

		Node* ppnode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

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


private:
	Node* _root = nullptr;
};

void Test_RBTree1()
{
	int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	RBTree<int, int> t1;
	for (auto e : a)
	{

		t1.Insert(make_pair(e, e));
		cout << e << "插入:" << t1.IsBalance() << endl;
	}

	t1.InOrder();
}

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-05-01 00:30:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-05-01 00:30:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-05-01 00:30:02       82 阅读
  4. Python语言-面向对象

    2024-05-01 00:30:02       91 阅读

热门阅读

  1. 剧情游戏如何制作?

    2024-05-01 00:30:02       27 阅读
  2. 跟我学C++中级篇——内联

    2024-05-01 00:30:02       28 阅读
  3. pytest.ini配置文件

    2024-05-01 00:30:02       25 阅读
  4. Three CSS2D 渲染器 月球绕地球旋转

    2024-05-01 00:30:02       30 阅读
  5. 华为鸿蒙HarmonyOS应用开发者高级认证答案

    2024-05-01 00:30:02       32 阅读
  6. Github 2024-04-24 C开源项目日报 Top9

    2024-05-01 00:30:02       28 阅读
  7. LeeCode 1728 任意图上博弈

    2024-05-01 00:30:02       33 阅读
  8. 爬虫 - 基于requests进行二次开发

    2024-05-01 00:30:02       29 阅读
  9. 算法训练营day28

    2024-05-01 00:30:02       30 阅读
  10. php变量创建和定义规则和常见常量

    2024-05-01 00:30:02       33 阅读