【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();
}

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
当然,我可以帮你实现一个基于红黑树的Map数据结构。下面是一个简单的C++代码示例: ```cpp #include <iostream> enum Color { RED, BLACK }; template <typename K, typename V> struct Node { K key; V value; Color color; Node* left; Node* right; Node* parent; // 构造函数 Node(const K& k, const V& v, Color c = RED) : key(k), value(v), color(c), left(nullptr), right(nullptr), parent(nullptr) {} }; template <typename K, typename V> class RBTreeMap { public: RBTreeMap() : root(nullptr) {} // 向红黑树中插入键值对 void insert(const K& key, const V& value) { Node<K, V>* node = new Node<K, V>(key, value); if (root == nullptr) { root = node; node->color = BLACK; } else { Node<K, V>* curr = root; Node<K, V>* parent = nullptr; while (curr != nullptr) { parent = curr; if (key < curr->key) curr = curr->left; else if (key > curr->key) curr = curr->right; else { curr->value = value; // 更新值 return; } } node->parent = parent; if (key < parent->key) parent->left = node; else parent->right = node; insertFixup(node); } } // 查找指定键的值 V find(const K& key) const { Node<K, V>* curr = root; while (curr != nullptr) { if (key < curr->key) curr = curr->left; else if (key > curr->key) curr = curr->right; else return curr->value; } return V(); // 未找到,返回默认值 } private: Node<K, V>* root; // 对红黑树进行修复以维持红黑树的性质 void insertFixup(Node<K, V>* node) { while (node->parent != nullptr && node->parent->color == RED) { if (node->parent == node->parent->parent->left) { Node<K, V>* uncle = node->parent->parent->right; if (uncle != nullptr && uncle->color == RED) { node->parent->color = BLACK; uncle->color = BLACK; node->parent->parent->color = RED; node = node->parent->parent; } else { if (node == node->parent->right) { node = node->parent; rotateLeft(node); } node->parent->color = BLACK; node->parent->parent->color = RED; rotateRight(node->parent->parent); } } else { Node<K, V>* uncle = node->parent->parent->left; if (uncle != nullptr && uncle->color == RED) { node->parent->color = BLACK; uncle->color = BLACK; node->parent->parent->color = RED; node = node->parent->parent; } else { if (node == node->parent->left) { node = node->parent; rotateRight(node); } node->parent->color = BLACK; node->parent->parent->color = RED; rotateLeft(node->parent->parent); } } } root->color = BLACK; } // 左旋 void rotateLeft(Node<K, V>* node) { Node<K, V>* rightChild = node->right; node->right = rightChild->left; if (rightChild->left != nullptr) rightChild->left->parent = node; rightChild->parent = node->parent; if (node->parent == nullptr) root = rightChild; else if (node == node->parent->left) node->parent->left = rightChild; else node->parent->right = rightChild; rightChild->left = node; node->parent = rightChild; } // 右旋 void rotateRight(Node<K, V>* node) { Node<K, V>* leftChild = node->left; node->left = leftChild->right; if (leftChild->right != nullptr) leftChild->right->parent = node; leftChild->parent = node->parent; if (node->parent == nullptr) root = leftChild; else if (node == node->parent->left) node->parent->left = leftChild; else node->parent->right = leftChild; leftChild->right = node; node->parent = leftChild; } }; int main() { RBTreeMap<int, std::string> map; map.insert(1, "one"); map.insert(2, "two"); map.insert(3, "three"); map.insert(4, "four"); std::cout << map.find(2) << std::endl; // 输出: two std::cout << map.find(5) << std::endl; // 输出: 空字符串(默认值) return 0; } ``` 这个示例使用红黑树实现了一个简单的Map数据结构。你可以使用`insert`方法向Map中插入键值对,使用`find`方法查找指定键的值。注意,这只是一个简单的实现,还可以根据需要进行扩展和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值