C++二叉搜索树,AVL树与红黑树
目录
- 搜索树
- key模型和key-value模型
- 搜索树实现
- AVL树
- 什么是AVL树
- AVL树的实现
- 插入操作详解
- 1.按照二叉搜索树规则插入
- 2.更新父节点的平衡因子
- 3.向上回溯并检查平衡,直至根节点
- 3.1右单旋
- 3.2左单旋
- 3.3左右双旋
- 3.3右左双旋
- AVL树模拟实现代码
- 红黑树
- 红黑树的性质
- 实现平衡的原因
- 红黑树插入操作
- 1.按照二叉搜索树规则插入
- 2.检查父节点颜色
- 3.调整颜色,旋转维持平衡
- 3.1 叔叔节点为红,调整颜色,无需旋转
- 3.2 叔叔节点不为红色,且为LL或者RR
- 3.2.1 叔叔节点不存在
- 3.2.2 叔叔节点存在且为黑
- 3.3 叔叔节点不为红,且为LR或者RL
- 3.3.1 叔叔节点不存在
- 3.3.2 叔叔节点存在且为黑
- 红黑树模拟实现代码
搜索树
搜索树,通常特指二叉搜索树,是一种基于二叉树的、具有特定排序性质的数据结构。
它的核心特性遵循 “左小右大” 原则:
- 任意节点的左子树上所有节点的值,都小于该节点本身的值。
- 任意节点的右子树上所有节点的值,都大于该节点本身的值。
- 默认情况下,左、右子树也都是二叉搜索树。
- (通常)不存在键值相等的节点。
核心操作与性能
由于其有序性,二叉搜索树支持以下高效操作:
- 查找:从根节点开始,比目标值小则进入左子树,大则进入右子树,直到找到或到达空节点。平均时间复杂度为 O(log n)。
- 插入:遵循查找路径,找到合适的空位置插入新节点,始终保持"左小右大"的规则。
- 删除:情况稍复杂,需处理待删除节点有0个、1个或2个子节点的情况,但核心仍是维护树的有序性。
局限性:性能退化问题
二叉搜索树的性能严重依赖于树的形状。
- 理想情况:树是完全平衡或接近完全平衡的,此时树高为 log₂n,所有操作效率都很高。
- 最坏情况:如果插入的数据是有序的(如1, 2, 3, 4, 5…),树会退化成一条链表。
- 此时,树高为 n。
- 查找、插入、删除的时间复杂度都退化为 O(n),失去了其高效的优势。
理想情况和最坏情况如下图所示:
key模型和key-value模型
Key模型是纯键存储结构,每个节点只包含一个键值(Key),不存储额外的数据值(Value)。它主要用于判断元素是否存在、数据去重和集合操作,关注的是"存在性"问题。STL中的set就是典型的Key模型实现,适用于黑白名单过滤、唯一性检查等场景,结构简单且内存占用较小。
Key-Value模型是键值对存储结构,每个节点包含键(Key)和对应的值(Value)。Key用于排序和快速查找,Value存储实际关联的数据。它主要用于建立映射关系,如字典、缓存系统和统计计数等。STL中的map和set都是Key-Value模型的典型实现,能够表达更复杂的对应关系,应用范围更加广泛。
搜索树实现
我们这里以key-value模型举例,key模型只需去除value即可
template<class K, class V>
struct BSTreeNode {BSTreeNode<K,V>* _left; // 左子节点指针BSTreeNode<K,V>* _right; // 右子节点指针K _key; // 键值,用于排序和比较V _value; // 存储的数据值// 构造函数,初始化键值对和子节点指针BSTreeNode(const K& key = K(), const V& value = V()):_key(key), _value(value), _left(nullptr), _right(nullptr) {}};
使用key和value进行节点构造,然后进行构造搜索树
插入操作 (Insert)
- 从根节点开始比较,小于当前节点往左,大于往右
- 找到空位置后插入新节点
查找操作 (Find)
比key大往右走,比key小往左走,相等则找到了,返回,走到空则查找失败,树中没有该节点.
删除操作 (Erase)
三种情况处理:
- 删除叶子节点:直接删除,父节点对应指针置空
- 删除只有一个子节点的节点:用子节点替代被删除节点
- 删除有两个子节点的节点:
- 找到左子树的最大节点或右子树的最小节点
- 交换键值对
- 转换为删除叶子节点或单子节点的情况
实现代码:
#pragma once#include<iostream>
#include<string>
#include<vector>
#include<set>
#include<cassert>
using namespace std;// 二叉搜索树节点模板类
template<class K, class V>
struct BSTreeNode {BSTreeNode<K,V>* _left; // 左子节点指针BSTreeNode<K,V>* _right; // 右子节点指针K _key; // 键值,用于排序和比较V _value; // 存储的数据值// 构造函数,初始化键值对和子节点指针BSTreeNode(const K& key = K(), const V& value = V()):_key(key), _value(value), _left(nullptr), _right(nullptr) {}
};// 二叉搜索树模板类
template<class K, class V>
class BSTree {typedef BSTreeNode<K, V> Node; // 节点类型别名public:// 默认构造函数BSTree():_root(nullptr){}// 拷贝构造函数 - 深拷贝BSTree(const BSTree& tree){_root = Copy(tree._root);}// 赋值运算符重载BSTree& operator=(const BSTree& tree){return BSTree(tree);}// 析构函数 - 释放所有节点内存~BSTree(){Destroy(_root);_root = nullptr;}// 插入键值对bool Insert(const K& key, const V& value){// 创建新节点Node* newNode = new Node(key, value);if (_root == nullptr) {// 空树情况,新节点作为根节点_root = newNode;}else {Node* parent = _root; // 记录父节点Node* cur = _root; // 当前遍历节点// 寻找插入位置while (cur) {parent = cur;if (key < cur->_key) cur = cur->_left; // 往左子树找else if (key > cur->_key) cur = cur->_right; // 往右子树找else return false; // 键值已存在,插入失败}// 在父节点的正确位置插入新节点if (key < parent->_key) {parent->_left = newNode;}else {parent->_right = newNode;}}return true; // 插入成功}// 查找指定键值的节点Node* Find(const K& key){Node* cur = _root;while (cur){if (key < cur->_key) cur = cur->_left; // 往左子树找else if (key > cur->_key) cur = cur->_right; // 往右子树找else return cur; // 找到目标节点}return nullptr; // 未找到}// 删除指定键值的节点bool Erase(const K& key){if (_root == nullptr) return false; // 空树情况Node* parent = _root; // 父节点指针Node* cur = _root; // 当前节点指针// 寻找要删除的节点while (cur && cur->_key != key) {parent = cur;if (key < cur->_key) cur = cur->_left;else cur = cur->_right;}if (cur == nullptr) return false; // 未找到要删除的节点// 情况1:删除的节点只有左子树或无子树if (cur->_right == nullptr){if (cur == parent->_left){parent->_left = cur->_left; // 父节点左指针指向cur的左子树}else if(cur == parent->_right){parent->_right = cur->_left; // 父节点右指针指向cur的左子树}else if (cur == _root) // 删除的是根节点{_root = cur->_left; // 根节点指向左子树}else{assert(false); // 不应该执行到这里}delete cur; // 释放节点内存}// 情况2:删除的节点只有右子树或无子树else if (cur->_left == nullptr){if (cur == parent->_left){parent->_left = cur->_right; // 父节点左指针指向cur的右子树}else if( cur == parent->_right){parent->_right = cur->_right; // 父节点右指针指向cur的右子树}else if (cur == _root) // 删除的是根节点{_root = cur->_right; // 根节点指向右子树}else{assert(false); // 不应该执行到这里}delete cur; // 释放节点内存}// 情况3:删除的节点有两个子树else {// 寻找左子树的最大节点(替代节点)Node* leftMaxParent = cur; // 替代节点的父节点Node* leftMax = cur->_left; // 替代节点while (leftMax->_right) {leftMaxParent = leftMax;leftMax = leftMax->_right;}// 交换当前节点和替代节点的键值对swap(cur->_key, leftMax->_key);swap(cur->_value, leftMax->_value);// 删除替代节点(现在替代节点最多只有一个左子树)if (leftMax == leftMaxParent->_left) {leftMaxParent->_left = leftMax->_left;}else {leftMaxParent->_right = leftMax->_left;}delete leftMax; // 释放替代节点内存}cout << ":" << key << endl; // 输出删除的键值(调试信息)return true; // 删除成功}// 中序遍历 - 按键值升序输出void InOrder(){_InOrder(_root);}// 判断树是否为空bool Empty(){return _root == nullptr;}private:// 递归销毁整棵树void Destroy(Node* root){if (root == nullptr) return;Destroy(root->_left); // 递归销毁左子树Destroy(root->_right); // 递归销毁右子树delete root; // 释放当前节点root = nullptr;}// 递归拷贝整棵树Node* Copy(Node* root){if (root == nullptr) return root;// 创建新节点Node* newnode = new Node(root->_key, root->_value);// 递归拷贝左右子树newnode->_left = Copy(root->_left);newnode->_right = Copy(root->_right);return newnode;}// 递归中序遍历void _InOrder(Node* root){if (root == nullptr) return;_InOrder(root->_left); // 遍历左子树// 输出当前节点的键值对cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right); // 遍历右子树}Node* _root = nullptr; // 根节点指针
};
AVL树
什么是AVL树
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下。
因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年 发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均 搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是AVL树。
使用平衡因子并不是必须的,而是其中一种实现方式。平衡因子 = 右子树高度 - 左子树高度
如果它有n个结点,其高度可保持在 O(log_2 n),搜索时间复杂度O(log_2 n)。
AVL树的实现
AVL树的大部分操作与搜索树相同,但是需要维护自身的平衡,因此在插入与删除的时候需要进行平衡调整。删除操作较为复杂,本文章暂未讲解,但是与插入操作相似,也是先按照搜索树删除,在更新平衡因子,如果不平衡在进行旋转。
插入操作详解
AVL树在插入新节点后,为了维持其平衡性(即任何节点的左右子树高度差绝对值不超过1),需要严格遵守以下核心3步:
1.按照二叉搜索树规则插入
- 从根节点开始,将新节点的键值与当前节点比较。
- 遵循“左小右大”的原则:
如果新节点键值 小于 当前节点键值,则递归进入左子树插入。
如果新节点键值 大于 当前节点键值,则递归进入右子树插入。
- 直到找到一个空位置,将新节点插入。
2.更新父节点的平衡因子
-
新节点插入后,其父节点的平衡因子必须更新。
-
更新规则如下:
如果新节点是作为左孩子插入的,则父节点的平衡因子 -1。
如果新节点是作为右孩子插入的,则父节点的平衡因子 +1。
3.向上回溯并检查平衡,直至根节点
- 从插入节点的父节点开始,自底向上地回溯到根节点,检查并更新沿途祖先节点的平衡因子。
- 对于每一个被访问的祖先节点(我们称之为当前节点),根据其更新后的平衡因子,采取以下行动:
当前节点的新平衡因子 | 含义 | 需要执行的操作 |
---|---|---|
0 | 插入后,该节点左右子树高度变得相等。 这意味着插入操作填补了原来较矮的一边,使得以该节点为根的子树总高度没有发生变化。 | 停止回溯。 因为子树高度未变,不会影响更上层祖先的平衡。 |
-1 或 1 | 插入后,该节点变得一边稍高。 这意味着以该节点为根的子树总高度增加了。 | 1. 继续向上回溯,去更新其父节点(即爷爷节点)的平衡因子。 2. 更新爷爷节点平衡因子的规则与第二步相同: - 如果当前节点是爷爷节点的左孩子,则爷爷节点平衡因子 -1。 - 如果当前节点是爷爷节点的右孩子,则爷爷节点平衡因子 +1。 |
-2 或 2 | 该节点严重不平衡,违反了AVL树的性质。 | 1. 立即停止回溯。 2. 对该节点进行旋转操作,以恢复平衡。 - 根据不平衡的具体情况(LL, LR, RR, RL),选择对应的旋转策略(单旋或双旋)。 3. 旋转后,以该节点为根的子树高度恢复到了插入前的状态,因此不会影响更上层的平衡,回溯过程自然结束。 |
示意流程图
更新中平衡因子不可能出现除了0,±1, ±2之外的情况,因为在插入之前已经是AVL树,符合性质 高度差不超过1(平衡因子为0,±1),因此在插入一个节点后最多改变1,最坏就是±2,然后旋转后变成平衡的
3.1右单旋
当某一个节点的平衡因子为-2,并且其左孩子的平衡因子为-1,那么就需要进行右单旋操作。
上图中h为高度,a,b,c均为高度为h的AVL子树。但是,a子树一定是平衡的,因为要想更新平衡因子至X节点,那么一定得从插入新节点的父亲节点开始一路向上一直更新,如果a子树不平衡,要么在插入之后变平衡,要么进行旋转后恢复平衡,不会一直向上更新,只有平衡树插入才会导致一直向上更新。
由于 a < Y < b < X < c (Y的值大于a子树的最大节点值,Y的值小于b节点的最小值,其余节点和子树比较也类似),那么我们就可以根据平衡树和搜索树规则将Y当做根节点,a子树为左孩子,X节点为右孩子,b,c子树分别为X的左右孩子。这就是一次右单旋情况。
3.2左单旋
当某一个节点的平衡因子为2,并且其右孩子的平衡因子为1,那么就需要进行左单旋操作。
左单旋几乎是与右单旋是镜像的操作,如果能理解右单旋,左单旋很容易就可以理解。
3.3左右双旋
当某一个节点的平衡因子为-2,并且其左孩子的平衡因子为1,那么就需要进行左右双旋操作。
如上图所示,插入节点在b,c子树的时候,就需要进行左右双旋,(先左单旋在右单旋),
3.3右左双旋
当某一个节点的平衡因子为2,并且其左孩子的平衡因子为-1,那么就需要进行右左双旋操作。
AVL树模拟实现代码
#pragma once#include<iostream>
#include<cassert>
using namespace std;template<class K, class V>
struct AVLTreeNode {AVLTreeNode<K, V>* _parent;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;K _key;V _value;int _bf;AVLTreeNode(const K& key = K(), const V& value = V()):_key(key), _value(value), _left(nullptr), _right(nullptr),_parent(nullptr),_bf(0) {}};template<class K, class V>
class AVLTree {typedef AVLTreeNode<K, V> Node;public:AVLTree() :_root(nullptr) {}AVLTree(const AVLTree& tree){_root = Copy(tree._root);}AVLTree& operator=(const AVLTree& tree){return AVLTree(tree);}~AVLTree(){Destroy(_root);_root = nullptr;}bool Insert(const K& key, const V& value) {Node* newNode = new Node(key, value);if (_root == nullptr) {_root = newNode;}else {Node* parent = _root;Node* cur = _root;while (cur) {parent = cur;if (key < cur->_key) cur = cur->_left;else if (key > cur->_key) cur = cur->_right;else return false;}if (key < parent->_key) {parent->_left = newNode;}else {parent->_right = newNode;}newNode->_parent = parent;cur = newNode;while ( parent != nullptr){if (cur == parent->_left) {parent->_bf--;if (abs(parent->_bf) != 1){if (parent->_bf == -2 && cur->_bf == 1){//左右双旋RotateLR(parent);}else if (parent->_bf == -2 && cur->_bf == -1){//右单旋RotateR(parent);}break;}}else {parent->_bf++;if (abs(parent->_bf)!= 1){if (parent->_bf == 2 && cur->_bf == 1){//左单旋RotateL(parent);}else if (parent->_bf == 2 && cur->_bf == -1){//右左双旋RotateRL(parent);}break;}}cur = parent;parent = parent->_parent;}}// cout << key << ":" << IsBalance() << endl;// InOrder();// cout << endl;return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (key < cur->_key) cur = cur->_left;else if (key > cur->_key) cur = cur->_right;else return cur;}return nullptr;}void InOrder(){_InOrder(_root);}bool Empty(){return _root == nullptr;}bool IsBalance(){int deep = 0;return _IsBalance(_root, &deep);}
private:void Destroy(Node* root){if (root == nullptr) return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}Node* Copy(Node* root){if (root == nullptr) return root;Node* newnode = new Node(root->_key, root->_value);newnode->_left = Copy(root->_left);newnode->_right = Copy(root->_right);return newnode;}bool _IsBalance(Node* root, int* deep){if (root == nullptr) return true;if (abs(root->_bf) > 1){cout << "key:" << root->_key << "bf error: " << root->_bf << endl;return false;}int ldeep = 0;int rdeep = 0;if (!(_IsBalance(root->_left, &ldeep) && _IsBalance(root->_right, &rdeep))) { return false; }if (abs(ldeep - rdeep) > 1){cout << "no balance: key->" << root->_key << endl;return false;}*deep = max(ldeep, rdeep)+1;return true;}void _InOrder(Node* root){if (root == nullptr) return;_InOrder(root->_left);//cout << root->_key << ":" << root->_value << endl;cout << root->_key << " ";_InOrder(root->_right);}void RotateR(Node* cur){Node* parentOfcur = cur->_parent;Node* left = cur->_left;cur->_left = left->_right;if(left->_right)left->_right->_parent = cur;left->_right = cur;cur->_parent = left;if (parentOfcur == nullptr){_root = left;left->_parent = nullptr;}else{if (parentOfcur->_left == cur){parentOfcur->_left = left;left->_parent = parentOfcur;}else{parentOfcur->_right = left;left->_parent = parentOfcur;}}left->_bf = 0;cur->_bf = 0;}void RotateL(Node* cur){Node* parentOfcur = cur->_parent;Node* right = cur->_right;cur->_right = right->_left;if(right->_left)right->_left->_parent = cur;right->_left = cur;cur->_parent = right;if (parentOfcur == nullptr){_root = right;right->_parent = nullptr;}else{if (parentOfcur->_right == cur){parentOfcur->_right = right;right->_parent = parentOfcur;}else{parentOfcur->_left = right;right->_parent = parentOfcur;}}right->_bf = 0;cur->_bf = 0;}void RotateRL(Node* cur){Node* parentOfcur = cur->_parent;Node* right = cur->_right;Node* leftOfright = right->_left;RotateR(right);RotateL(cur);if (leftOfright->_bf == -1){cur->_bf = 0;right->_bf = 1;leftOfright->_bf = 0;}else if (leftOfright->_bf == 1){cur->_bf = -1;right->_bf = 0;leftOfright->_bf = 0;}else{cur->_bf = right->_bf = leftOfright->_bf = 0;}}void RotateLR(Node* cur){Node* parentOfcur = cur->_parent;Node* left = cur->_left;Node* rightOfleft = left->_right;RotateL(left);RotateR(cur);if (rightOfleft->_bf == -1){cur->_bf = 1;left->_bf = 0;rightOfleft->_bf = 0;}else if (rightOfleft->_bf == 1){cur->_bf = 0;left->_bf = -1;rightOfleft->_bf = 0;}else{cur->_bf = left->_bf = rightOfleft->_bf = 0;}}Node* _root = nullptr;
};
红黑树
红黑树也是自平衡的二叉搜索树,通过引入规则和颜色来控制平衡,同AVL树一样都是搜索树的增强版,不同的是,AVL树是严格平衡,而红黑树则相对宽松一点,最长路径不大于最短路径的二倍,因为实现起来较AVL树相对容易,因此也是map和set最常用的底层容器。本文也同样只讲解了红黑树的插入操作,删除操作较为复杂,并未讲解,其他操作与搜索树相同。
红黑树的性质
- 每个节点要么是红色,要么是黑色
- 根节点是黑色的
- 每个叶子节点(NIL节点)都是黑色的
- 不能有两个相邻的红色节点(即红色节点的父节点和子节点不能是红色的)
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点
实现平衡的原因
第二条性质为根节点必须为黑,是因为根节点作为所有路径的公共祖先,根节点的颜色直接影响整个树的平衡状态。如果根节点为红色,在某些情况下可能导致性质冲突,特别是在插入操作时可能引发不必要的重新平衡。因此强制根节点为黑色可以更好的维护性质5。
第三条性质所说的叶子节点特指空节点(NIL节点),而是存放实际数据的叶子节点中的左右孩子指针(代码中为叶子节点里实际存放的空指针,在数据结构上抽象为NIL节点)。将所有实际的空指针统一视为黑色的NIL节点,极大地简化了路径长度的计算和平衡条件的判断。这种设计使得算法无需特别处理各种边界情况,每个节点都可以被视为拥有完整的左右子树(即使是NIL),从而保证了从任一节点到所有叶子节点的路径定义明确,这也是为了更好的维护性质5。
而通过第4和5条性质,就可以控制红黑树为平衡树:
在红黑树中,如果存在全黑的路径,那一定是最短的,如果存在一黑一红相间的路径,那一定是最长的(通过性质4不能有连续红节点约束),在通过性质5,就使得最短路径和最长路径中含有相等数量的黑色节点,则该最长路径是最短路径的二倍,但在实际的红黑树总可能并没有同时存在这两条路径,但是其他的路径长度一定是在最短和最长的区间内,因此在一颗实际存在的红黑树中,最长的路径一定不会大于最短的路径的2倍。
红黑树插入操作
红黑树插入需要维护自身性质,需要通过调整颜色,旋转来维护性质,从而达到平衡的目的.
插入规则可以归结为以下3步.
1.按照二叉搜索树规则插入
-
从根节点开始,将新节点的键值与当前节点比较。
-
遵循“左小右大”的原则:
如果新节点键值 小于 当前节点键值,则递归进入左子树插入。
如果新节点键值 大于 当前节点键值,则递归进入右子树插入。
-
直到找到一个空位置,将新节点插入。
2.检查父节点颜色
- 新节点的颜色为红色,若是插入节点的父亲节点为黑色,则本身符合红黑树性质,无需调整,插入结束.
- 若插入节点的父亲节点为红色,则需进行调整、执行步骤3
3.调整颜色,旋转维持平衡
若需要调整颜色,则父节点一定是黑色,那么又可以得出,爷爷节点(父节点的父节点)一定是黑色,因为不能出现连续的红色节点,那么这一步的调整则需要根据叔叔节点(父节点的兄弟节点)来判断.
为了方便讲解,我们给这些节点进行缩写命名
爷爷节点 grandfather: g节点
父亲节点 parent: p节点
叔叔节点 uncle: u节点
新插入节点 child: c节点
3.1 叔叔节点为红,调整颜色,无需旋转
如图所示,只要叔叔节点存在且为黑,那么只需将父节点和叔叔节点置为黑,将爷爷节点置为红,即可完成该步调整.
与AVL树不同,c和p可以是p和g的任意一边的子树,无需判断左右.
这种做法可以理解为:通过爷爷节点的路径黑色节点必包含爷爷节点,那么将他分别放入左右子树的根节点也可以进行平替,通过爷爷节点的路径黑色节点树量没变.
注意:爷爷节点不一定为根节点,而是一颗子树的起始,因此在变为红色后还需接着向上调整,只需将爷爷节点当做新插入的节点c,按照调整规则(步骤2,3)接着向上调整即可.
3.2 叔叔节点不为红色,且为LL或者RR
如图所示:
LL: p为g的左孩子,c为p的左孩子
RR: p为g的右孩子,c为p的右孩子
X: 所有路径黑色节点为h+1的子红黑树
Y: 所有路径黑色节点为h的子红黑树
Y子树经过了u节点,则经过u节点的每条路径上的黑色节点数量为h+1,根据性质5,则X子树的路径上的黑色节点数量都为h+1
但是叔叔节点不为红对应着2种情况
叔叔节点不存在
这种情况下**,c一定是新插入的节点**,因为叔叔节点不存在,g的右孩子为NIL节点,即这条路径上的黑色节点数量为2(g,NIL),那么c如果不是新插入的节点,那么其子树中一定存在黑色节点,加上其叶子节点NIL,没条路径上的黑色节点数量超过了2,不符合性质5.
叔叔节点存在且为黑,此时cur一定不是新插入节点,之前的颜色一定是黑色,现在是红色是因为在变色调整过程中将其调整为红色了
这两种处理方法相同,均为以g点进行旋转,然后将g的颜色置为红,将p的颜色置为黑
3.2.1 叔叔节点不存在
3.2.2 叔叔节点存在且为黑
总结一下,可以概括为:
当叔叔节点不为红,
LL: 对g进行右单旋,g变红,p变黑.
RR: 对g进行左单旋,g变红,p变黑
3.3 叔叔节点不为红,且为LR或者RL
LR: p为g的左孩子,c为p的右孩子
RL: p为g的右孩子,c为p的左孩子
与3.2相同,该情况下叔叔节点不为红也对应着2种情况,解释也与3.2一模一样
3.3.1 叔叔节点不存在
对父亲节点进行一次单旋就可以转化为3.2.1.
3.3.2 叔叔节点存在且为黑
对父亲节点进行一次单旋就可以转化为3.2.2
需要注意的是:进行旋转后,p与c的相对位置发生变化,在转化后执行3.2的步骤是要注意对应关系(3.3 p 对应3.2 c, 3.3 c对应 3.2 p)
总结一下,可以概括为:
当叔叔节点不为红
LR: 对p进行左单旋,然后对g进行右单旋,g变红,c变黑.
LL: 对p进行右单旋,然后对g进行左单旋,g变红,c变黑
红黑树模拟实现代码
#pragma once#include<iostream>#include<cassert>
using namespace std;enum Color{ RED, BLACK};
template<class K, class V>
struct RBTreeNode {RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;K _key;V _value;Color _color;RBTreeNode(const K& key = K(), const V& value = V()):_key(key), _value(value), _left(nullptr),_right(nullptr), _color(RED), _parent(nullptr){}};template<class K, class V>
class RBTree {typedef RBTreeNode<K, V> Node;public:RBTree() :_root(nullptr) {}RBTree(const RBTree& tree){_root = Copy(tree._root);}RBTree& operator=(const RBTree& tree){return BSTree(tree);}~RBTree(){Destroy(_root);_root = nullptr;}bool Insert(const K& key, const V& value) {Node* newNode = new Node(key, value);if (_root == nullptr) {_root = newNode;}else {Node* parent = _root;Node* cur = _root;while (cur) {parent = cur;if (key < cur->_key) cur = cur->_left;else if (key > cur->_key) cur = cur->_right;else return false;}if (key < parent->_key) {parent->_left = newNode;}else {parent->_right = newNode;}newNode->_parent = parent;cur = newNode;while (parent != nullptr && parent != _root && parent->_color == RED){Node* grandFather = parent->_parent;Node* uncle = (parent == grandFather->_left ? grandFather->_right : grandFather->_left);if (uncle != nullptr && uncle->_color == RED){parent->_color = uncle->_color = BLACK;grandFather->_color = RED;cur = grandFather;parent = grandFather->_parent;}else {//(uncle == nullptr || uncle->_color == BLACK)if (parent == grandFather->_left ){// g// p u// cif (cur == parent->_left){RotateR(grandFather);parent->_color = BLACK;grandFather->_color = RED;}// g// p u// celse{RotateLR(grandFather);grandFather->_color = RED;cur->_color = BLACK;}}else {// g// u p// cif (cur == parent->_right){RotateL(grandFather);parent->_color = BLACK;grandFather->_color = RED;}// g// u p// celse{RotateRL(grandFather);grandFather->_color = RED;cur->_color = BLACK;}}break;}}}_root->_color = BLACK;return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (key < cur->_key) cur = cur->_left;else if (key > cur->_key) cur = cur->_right;else return cur;}return nullptr;}void InOrder(){_InOrder(_root);}bool Empty(){return _root == nullptr;}bool IsBalance(){int blackNum = 0;Node* cur = _root;while (cur){if (cur->_color == BLACK)blackNum++;cur = cur->_right;}return _IsBalance(_root, blackNum, 0);}
private:void Destroy(Node* root){if (root == nullptr) return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}Node* Copy(Node* root){if (root == nullptr) return root;Node* newnode = new Node(root->_key, root->_value);newnode->_left = Copy(root->_left);newnode->_right = Copy(root->_right);return newnode;}bool _IsBalance(Node* root, int blackNum, int selfBlackNum){if (root == nullptr){if (blackNum != selfBlackNum){cout << "Black number error, blackNum: " << blackNum << " , selfBlackNum: " << selfBlackNum << endl;return false;}else{//cout << "blackNum: " << blackNum << " , selfBlackNum: " << selfBlackNum << endl;return true;}}if (root->_color == RED && root->_parent->_color == RED){cout << "parent and cur is RED, key:" << root->_key << endl;return false;}if (root->_color == BLACK) selfBlackNum++;return _IsBalance(root->_left, blackNum, selfBlackNum) && _IsBalance(root->_right, blackNum, selfBlackNum);}void RotateR(Node* cur){Node* parentOfcur = cur->_parent;Node* left = cur->_left;cur->_left = left->_right;if (left->_right)left->_right->_parent = cur;left->_right = cur;cur->_parent = left;if (parentOfcur == nullptr){_root = left;left->_parent = nullptr;}else{if (parentOfcur->_left == cur){parentOfcur->_left = left;left->_parent = parentOfcur;}else{parentOfcur->_right = left;left->_parent = parentOfcur;}}}void RotateL(Node* cur){Node* parentOfcur = cur->_parent;Node* right = cur->_right;cur->_right = right->_left;if (right->_left)right->_left->_parent = cur;right->_left = cur;cur->_parent = right;if (parentOfcur == nullptr){_root = right;right->_parent = nullptr;}else{if (parentOfcur->_right == cur){parentOfcur->_right = right;right->_parent = parentOfcur;}else{parentOfcur->_left = right;right->_parent = parentOfcur;}}}void RotateRL(Node* cur){Node* parentOfcur = cur->_parent;Node* right = cur->_right;Node* leftOfright = right->_left;RotateR(right);RotateL(cur);}void RotateLR(Node* cur){Node* parentOfcur = cur->_parent;Node* left = cur->_left;Node* rightOfleft = left->_right;RotateL(left);RotateR(cur);}void _InOrder(Node* root){if (root == nullptr) return;_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}Node* _root = nullptr;
};