红黑树的插入(有图)

红黑树的概念

红黑树也是一颗二叉搜索树,红黑树添加了一个新的存储结构,表示节点的颜色,红色或黑色,通过对任何一条从根到叶子节点的路径上的各个节点的颜色进行约束,红黑树确保最长路径不会超过最短路径的2倍

红黑树的规则
  1. 每个节点不是红色就是黑色

  2. 根节点必须是黑色

  3. 如果一个节点是红色,那么它的两个孩子节点必须是黑色的,也就是说任意一条路径上不会有连续的红色节点

  4. 对于任意一个节点,从该节点到其所有空节点的路径上,均包含相同数量的黑色节点

规则4的约束使红黑树的最小路径是确定的,就是全黑色节点 :所有s路径都有相同的黑色节点数量,红色节点的数量是没有约束的,那最短的路径就一定是没有红色节点的路径,那就是全黑色节点

规则3和红黑树的概念一起约束了红黑树的最长路径是:一黑一红 ,路径不能有连续的红色节点,同时最长路径不能超过最短路径的2倍,则最长的路径一定是红黑交替的,所以最长路径就是一黑一红

规则2确保了从黑色开始

红黑树的插入

1.新插入的节点,颜色一定是红色,不能是黑色,黑色一定违反规则4(新节点所在的路径上黑色节点数量 + 1,其他路径上的黑色节点数量不变),插入红色可能违反规则3(可能插入位置的父节点也是红色),但也可能不会违反规则,空树可以插入黑色节点

插入红色同样可能违反规则,但违反的是规则3,只需要对这条路径及左或右的路径的节点进行颜色修改

但如果是黑色节点,违反规则4,就需要对所有路径进行颜色的修改,代价很大,所以在都会违反规则的情况下,选择违反规则3

2.新插入节点,父亲是黑的,就结束,因为新节点是红色,父亲可以是黑色,不会违反规则,就直接结束即可

3.插入新节点,新节点c是红的,父亲是红的,需要继续分析,爷爷g一定是黑的 ,父亲p是红的,新节点是红的,关键变化看叔叔u

插入前整颗树是一颗红黑树,父亲是红色的,那么爷爷一定是黑色的,因为不能有连续的红色节点出现

要讨论叔叔的情况, 一共有3种

1.叔叔存在且为红

2.叔叔存在且为黑

3.叔叔不存在

情况1:变色
  • c为红,p为红,g为黑,u存在且为红

插入的节点为红色, 父亲也是红色,那么违反了规则3, 所以此时就需要修改父亲的颜色(如果修改新节点的颜色就是相当于是插入黑色节点, 就更难处理), 将其修改为黑色来保证不会违反规则3

但是修改完父亲的颜色将其变为黑色又会出现一个问题, 那就是这条路径上多出了一个黑色节点, 违反了规则4

这时就是叔叔的存在意义了, 将叔叔一同变为黑色, 那么其叔叔所在的路径上也多出个黑色节点, 同时爷爷是黑色的, 只需要让爷爷变为红色, 那么父亲跟叔叔所在的路径就会同时减少一个红色节点, 就能保证不会违反规则

这是完成一次变色, 如果爷爷的父亲也是红色, 那么将爷爷变为红色就违反了规则3, 所以此时就需要继续向上变色, 一直到爷爷的父亲是黑色, 这时变色才算彻底完成

下面用抽象的情况来看

这里 hb 表示的是树中黑色节点的高度, 可以是任意值, 只要是满足图上情况, 就都是变色逻辑

a, b子树的 hb 由 hb - 1 变为 hb 是因为, 在将 x 节点转换为红色是, x 的左右孩子都变为了黑色, 就是a, b子树的根都变为了黑色, 所以hb 要 + 1

情况2:旋转 + 变色
  • c为红,p为红,g为黑, u不存在或存在为黑

u不存在,则c一定是新增的节点

如图所示, u不存在, 就说明 g没有右子树, 已知 p 是红, g 是黑 左右子树首先有 g 一个黑色节点, 如果c不是新增节点, 那么 c 的子树一定还有黑色节点, 而 u 所在的子树只有 g 一个黑色节点, 会违反规则4, 也就说明 c 一定是新增节点

u存在为黑,则c一定不是新增的节点

如图,图中展示的是 u存在且为黑的情况下,如果 c 是新插入的节点, 如中 u 所在的路径的黑色节点数量为2, 但 p 节点所在的路径只有一个黑色节点,就说明原来 p 接待你的子树种一定还有一个黑色节点,所以c不可能是新插入的节点

所以u存在为黑的情况如下图所示

如果单纯按照上面的规则进行变色,会产生下面的情况

仔细观察会发现,左子树的黑色节点的数量是 hb + 1,而右子树的黑色节点数量是 hb,违反了规则4,所以单纯的变色是无法解决问题的

需要进行旋转, 先旋转,再变色 (旋转的相关逻辑在另一篇博客中有详细的叙述)

  • 单旋的场景如下 : 插入节点是在最左变或最右边,分别使用右单旋或左单旋, 旋转后, p变为了新树的根, g, c分别变为左右子树, 只需要将p 变黑, 将 g 变红即可
c++ 复制代码
// 右单旋
//     g
//   p   u
// c

注:这里演示的是子树中只有一个黑色节点,即hb = 1 的情况,更多情况可以自行推导

c++ 复制代码
// 右单旋
//    g
//  u   p
//        c

u存在为黑的右单旋与左单旋相似,不再进行演示

  • 双旋场景如下 : 插入中间位置, 根据位置不同分别进行左右双旋或右左双旋, 旋转过后, c 变为新的根, 只需要让 c 变为黑色, g 变为红色就行
c++ 复制代码
// 左右双旋
//    g
//  p   u
//    c

注:这里演示的是所有的抽象情况,具体情况可自行推导

c++ 复制代码
// 右左双旋
//    g
//  u   p
//    c

综上所述, 在 c为红,p为红,g为黑, u不存在或存在为黑的情况下, 需要先旋转再进行变色, 变色的逻辑如下 :

1.如果进行单旋, 旋转后, 将 g 变为红色, p 变为黑色

2.如果进行双旋, 旋转后, 将 g 变为红色, c 变为黑色

红黑树的查找

使用二叉搜索树的规则进行查找即可

  • 比当前节点小,就向左找

  • 比当前节点大,就向右找

红黑树的验证

如何验证一颗树是红黑树或者实现了一颗红黑树要如何验证是否正确

根据规则,红黑树中最长的路径的长度要小于等于最短的路径的2倍,是否可以根据整个来判断 ? 答案是不行,就算满足了整个条件,树中的颜色可能不正确,而且根据最开始提到的,红黑树是通过4条规则来保证上面的条件的,所以要验证一颗树是否是红黑树,正确的做法是验证这颗树是否符合红黑树的规则

  1. 节点不是红色就是黑色,使用枚举定义天然保证这一点

  2. 根节点是黑色,很简单,验证一下根节点的颜色就行

第3条与第4条规则的验证一起进行,规则4是需要找到每一条路径的黑色节点的数量,一定要遍历二叉树,规则3需要判断红色节点的孩子是不是黑色节点,如果根规则4结合起来,在验证规则4遍历链表的时候,通过孩子去判断父亲节点是否是红色,就能顺便验证规则3,所以验证的逻辑如下

  • 前序遍历整颗树,定义变量cur为当前节点

  • 如果cur是红色,parent也是红色就有问题,返回颜色问题

  • 如果cur是红色,parent是黑色,没问题

  • 如果cur是黑色,parent是什么颜色都没问题,同时路径黑色节点数量++

  • 等cur走到空节点时,将路径黑色节点的数量与参照值对比即可(传入一个参照值即可,通过遍历任意一条路径得到)

红黑树的代码实现
c++ 复制代码
// RBTree.h
#pragma once

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

namespace xuan {
	// 红黑树的节点结构
	enum color {
		RED,
		BLACK
	};

	template<class K, class V>
	class RBTreeNode {
	public:
		pair<K, V> _kv;
		RBTreeNode* _left;
		RBTreeNode* _right;
		RBTreeNode* _parent;
		color _col;

		RBTreeNode(const pair<K,V>& kv)
			:_kv(kv)
			,_left(nullptr)
			,_right(nullptr)
			,_parent(nullptr)
		{}
	};

	template<class K,class V>
	class RBTree {
		using Node = RBTreeNode<K, V>;
	public:
		bool insert(const pair<K, V>& kv){
			// 空树直接插入黑色节点
			if (_root == nullptr) {
				_root = new Node(kv);
				_root->_col = BLACK;
				return true;
			}

			Node* cur = _root;
			Node* parent = nullptr;

			// 非空循环查找插入位置
			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* grandfather = parent->_parent;

				//   g
				// p   u
				if (parent == grandfather->_left) {
					Node* uncle = grandfather->_right;

					if (uncle && uncle->_col == RED) {
						// 叔叔存在且为红,只需要变色
						grandfather->_col = RED;
						parent->_col = BLACK;
						uncle->_col = BLACK;

						// 变完色继续向上更新
						cur = grandfather;
						parent = cur->_parent;
					}
					else {
						// 叔叔存在且为黑或不存在
						//     g
						//   p   u
						// c
						if (cur == parent->_left) {
							// 单旋后变色
							RotateR(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						else {
							// 双旋后变色
							//    g
							//  p   u
							//    c
							RotateL(parent);
							RotateR(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}

						break;
					}
				}
				//   g
				// u   p
				else {
					Node* uncle = grandfather->_left;

					if (uncle && uncle->_col == RED) {
						// 叔叔存在且为红,只需变色
						grandfather->_col = RED;
						parent->_col = BLACK;
						uncle->_col = BLACK;

						// 变色完向上更新
						cur = grandfather;
						parent = cur->_parent;
					}
					else {
						if (cur == parent->_right) {
							// 存在且为黑或不存在
							//    g
							//  u   p
							//        c
							RotateL(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						else {
							//    g
							//  u   p
							//    c
							RotateR(parent);
							RotateL(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}

						break;
					}
				}
			}
			_root->_col = BLACK;

			return true;
		}

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

			subR->_left = parent;
			parent->_right = subRL;

			Node* pParent = parent->_parent;
			parent->_parent = subR;

			if (pParent) {
				if (pParent->_left == parent) {
					pParent->_left = subR;
				}
				else {
					pParent->_right = subR;
				}
			}
			else {
				_root = subR;
			}

			subR->_parent = pParent;
		}

		void RotateR(Node* parent) {
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			if (subLR) {
				subLR->_parent = parent;
			}

			subL->_right = parent;
			parent->_left = subLR;

			Node* pParent = parent->_parent;
			parent->_parent = subL;

			if (pParent) {
				if (pParent->_left == parent) {
					pParent->_left = subL;
				}
				else {
					pParent->_right = subL;
				}
			}
			else {
				_root = subL;
			}

			subL->_parent = pParent;
		}

		Node* find(const K& key) {
			Node* cur = _root;

			while (cur) {
				if (cur->_kv.first > key) {
					cur = cur->_left;
				}
				else if (cur->_kv.first < key) {
					cur = cur->_right;
				}
				else {
					return cur;
				}
			}

			return nullptr;
		}

		bool IsBalance() {
			// 空树也是红黑树
			if (_root == nullptr)
				return true;

			// 检查规则2
			if (_root->_col != BLACK)
				return false;

			// 参考值
			int refNum = 0;

			// 遍历任意一条路径找到黑色节点的参考值
			Node* cur = _root;
			while (cur) {
				if (cur->_col == BLACK)
					refNum++;

				cur = cur->_left;
			}

			return check(_root, 0, refNum);
		}

		void InOrder() {
			_InOrder(_root);
		}

	private:
		bool check(Node* root, int blackNum, const int refNum) {
			if (root == nullptr) {
				// 走到一条路径的最后,如果黑色节点数量不等于参考值就说明有问题
				if (blackNum != refNum) {
					cout << "黑色节点数量异常" << endl;
					return false;
				}
				else
					return true;
			}

			// 如果孩子的颜色是红色且父亲也是红,即说明颜色有问题
			if (root->_col == RED && root->_parent->_col == RED) {
				cout << "连续红色节点" << endl;
				return false;
			}
			
			if (root->_col == BLACK) 
				blackNum++;

			// 左右子树都满足条件才行
			return check(root->_left, blackNum, refNum)
				&& check(root->_right,blackNum, refNum);
		}

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

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

		Node* _root = nullptr;
	};
}
相关推荐
John.Lewis2 小时前
C++进阶(12)附加学习:STL之空间配置器(了解)
开发语言·c++·笔记
汉克老师2 小时前
GESP2023年12月认证C++三级( 第三部分编程题(2、单位转换))
c++·string·单位转换·gesp三级·gesp3级
cpp_25013 小时前
P2347 [NOIP 1996 提高组] 砝码称重
数据结构·c++·算法·题解·洛谷·noip·背包dp
Hugh-Yu-1301233 小时前
二元一次方程组求解器c++代码
开发语言·c++·算法
楼田莉子3 小时前
同步/异步日志系统:日志落地模块\日志器模块\异步日志模块
linux·服务器·c++·学习·设计模式
文祐3 小时前
C++类之虚函数表及其内存布局
开发语言·c++
小狄同学呀4 小时前
同样的global,不同的audioLibPath——记一次诡异的内存错位
c++·windows
编程大师哥4 小时前
C++类和对象
开发语言·c++·算法
Rabitebla4 小时前
C++ 和 C 语言实现 Stack 对比
c语言·数据结构·c++·算法·排序算法