当前位置: 首页 > news >正文

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)

  1. 从根节点开始比较,小于当前节点往左,大于往右
  2. 找到空位置后插入新节点

查找操作 (Find)

​ 比key大往右走,比key小往左走,相等则找到了,返回,走到空则查找失败,树中没有该节点.

删除操作 (Erase)

三种情况处理:

  1. 删除叶子节点:直接删除,父节点对应指针置空
  2. 删除只有一个子节点的节点:用子节点替代被删除节点
  3. 删除有两个子节点的节点
    • 找到左子树的最大节点或右子树的最小节点
    • 交换键值对
    • 转换为删除叶子节点或单子节点的情况

实现代码:

#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最常用的底层容器。本文也同样只讲解了红黑树的插入操作,删除操作较为复杂,并未讲解,其他操作与搜索树相同。

红黑树的性质

  1. 每个节点要么是红色,要么是黑色
  2. 根节点是黑色的
  3. 每个叶子节点(NIL节点)都是黑色的
  4. 不能有两个相邻的红色节点(即红色节点的父节点和子节点不能是红色的)
  5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点

实现平衡的原因

第二条性质为根节点必须为黑,是因为根节点作为所有路径的公共祖先,根节点的颜色直接影响整个树的平衡状态。如果根节点为红色,在某些情况下可能导致性质冲突,特别是在插入操作时可能引发不必要的重新平衡。因此强制根节点为黑色可以更好的维护性质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种情况

  1. 叔叔节点不存在

    这种情况下**,c一定是新插入的节点**,因为叔叔节点不存在,g的右孩子为NIL节点,即这条路径上的黑色节点数量为2(g,NIL),那么c如果不是新插入的节点,那么其子树中一定存在黑色节点,加上其叶子节点NIL,没条路径上的黑色节点数量超过了2,不符合性质5.

  2. 叔叔节点存在且为黑,此时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;
};
http://www.dtcms.com/a/492382.html

相关文章:

  • 阿里云ACK多个Service绑定单个SLB实践
  • 电脑硬盘和内存查询和分配
  • 公众电影网站怎么做保洁公司在哪个网站做推广比较好
  • DeerFlow多智能体项目分析-架构和项目入口
  • 湖南微信网站广州交通站场建设管理中心网站
  • 【UI】像素颜色格式
  • 网站建设费用表wordpress特别慢
  • 管理学习网站旅游网网站建设
  • 私募基金公司网站建设盘锦化工网站建设
  • 成绩查询网站开发潍坊快速网站排名
  • 广州模板建站系统wordpress 文字不显示
  • 企业网站建设方案书模板wordpress响应式concise主题
  • 衡石科技HQL与Agentic BI技术深度解析:构建下一代智能数据分析引擎
  • 淄博比较好的网站建设公司怎么用织梦源代码做网站
  • 智能SQL审核优化工具 PawSQL 月度更新 | 正确性优先、兼容性与可观测性加强
  • 网站备案icp过期青岛网站设计建议i青岛博采
  • 西安网站手机网站建设专业的上海网站建设公司哪家好
  • Kubernetes:控制器 - HPA
  • 网站开发入那个科目网页设计常用代码
  • 深圳网站制作就找兴田德润公司申请邮箱怎么申请
  • 网站推广建设期游戏公司招聘网站
  • 视觉差 网站邢台网站建设哪家公司好
  • Windows Search 服务
  • 网站优化及推广顺德网站制作案例价位
  • 北京品牌网站建设公司排名做图表的网站知乎
  • 点控云智能短信:重构企业与用户的连接,让品牌沟通更高效
  • NewStarCTF2025-Week1-PWN
  • 个人网站建站的流程网站设计过程
  • 网站开发合同受托方辽宁建设工程信息网业绩录入规定
  • 英文网站建设维护网络技术培训机构