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

红黑树的介绍

推荐使用电脑浏览器浏览
前言:本文介绍了红黑树的定义和构建,同时介绍了简易版红黑树插入的功能和代码实现,并未涉及红黑树的删除功能。

推荐先观看这个视频【红黑树 - 定义, 插入, 构建】
这是一个从B站转载过来的一个视频,我认为这个视频的动画演示能让观众形象直观地了解红黑树的操作。
防止链接失效而找不到视频,下面提供一张照片,可以去寻找该UP主来观看此视频。
视频的博主

红黑树的介绍

  • 1. 红黑树的概念
  • 2. 红黑树的规则
    • 2.1 五项规则
    • 2.2 规则带来的约束
  • 3. 红黑树的效率
  • 4. 红黑树的实现
    • 4.1 红黑树的结构
    • 4.2 红黑树的插入过程梳理
      • 4.2.1 红黑树中插入一个值的大概过程
      • 4.2.2 uncle结点的三种情况
      • 4.2.3 情况1:不需要进行任何操作
      • 4.2.4 情况2:变色
      • 4.2.5 情况3:单旋+变色
      • 4.2.6 情况4: 双旋+变色
    • 4.3 红黑树的插入代码实现
    • 4.4 检查红黑树是否平衡
  • 5. 检查红黑树是否构建成功
  • 6. 附录(红黑树的代码)

1. 红黑树的概念

  红黑树是一颗二叉搜索树。它也算是一棵平衡二叉搜索树,和 AVL树相比,红黑树的平衡显得比较宽松。通过对任何一条从根到叶子(的路径上的各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出 2 倍,因此是接近平衡的。它和AVL树类似,都在添加和删除的时候通过旋转操作保持二叉树的平衡,以求更高效的查询性能。

注: 这里的叶子结点不是一条路径的最后一个结点,而是最后一个结点的孩子结点,即空结点。如从根结点开始一直走左孩子指针走到最后,叶子结点是外部结点,也就是空结点,我们常说指向空结点,但是一般又不会特意去画出这个空结点。
我们画图能看到的一般都是内部结点,一些有数据的结点。

  • 在AVL树中,可以采用平衡因子的方法来快速判断这个结点代表的子树是否平衡,且平衡因子的绝对值不大于 1
  • 在红黑树中,采用一个储存位来表示结点的颜色,颜色只能是红色或者黑色。红黑树通过这样的一个方法来定义平衡,只要不破坏这个规则就算是平衡(算是相对平衡吧):对于任意一个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量黑色结点

与AVL树相比,红黑树牺牲了部分平衡性,以换取插入/删除操作时较少的旋转操作,整体来说性能要优于AVL树。


2. 红黑树的规则

2.1 五项规则

红黑树的规则有以下五点:

  • 特性1:每个结点不是红色就是黑色
    规则1示意图

  • 特性2:根节点一定是黑色的
    规则2示意图

  • 特性3:叶子结点(NIL)一定是黑色的
    特性3示意图

  • 特性4:如果一个结点是红色的,则它的两个孩子结点必须是黑色的。(也就是说任意一条路径上不会有连续的红色结点)
    特性4示意图

  • 特性5:从任意一个结点到其每个叶子结点(NIL)的所有简单路径上,均包含想通数量的黑色结点。
    特性5示意图

网络上流传的口诀:

  • 子黑红(对应规则1,结点非黑即红),
  • 根叶黑(对于规则2和3,根结点和叶子结点为黑色)
  • 不红红(对应规则4,不出现连续红色结点)
  • 黑路同(对应规则5,从任意结点出发,到其所有后代叶子结点的路径上包含相同数量的黑色结点)

根据这些规则,我们至少可以提前知道,红黑树的插入过程中,新增结点的颜色应该设置为红色。
那么为什么要设置成红色呢?

黑色: 因为规则5要求从某一个结点出发到其每个叶子结点的路径上有相同数量的黑色结点。当我们给新增结点涂上黑色之后,那么,经过新增结点的路径的黑色结点数量就会 +1,而未经过新增结点的路径的黑色结点数量则不发生改变。但是黑色结点数量不发生改变的路径数量肯定是比黑色结点数量发生改变的路径数量要多得多,这会导致重新这棵树符合规则5造成了麻烦的问题。


红色: 但是设置成红色,这只会违反规则4不能出现连续红色结点的情况,而且这是一种可能发生的情况。当新增结点的父结点为黑色时,新增结点设置成红色不会违反规则4。当新增结点的父结点也为红色时,通过调色或者旋转加调色就能让这棵树重新符合规则4,且逻辑简单,一次调整只需要用到四个结点。调整操作后面再介绍。

2.2 规则带来的约束

  • 红黑树如何确保最长路径不超过最短路径的 2 倍:
    • 由规则5可知,从根到NULL结点的每条路径都有相同数量的黑色结点,所以在极端情况下,最短路径是一条全是黑色结点的路径,假设最短路径长度为 bh(black height)。
    • 根叶黑不红红 可知,任意一条路径上不会有连续的红色结点,所以极端场景下,最长的路径就是一黑一红间隔组成,那么最长路径的长度为 2*bh
    • 综合上面的分析和五点规则而言,理论上的全黑最短路径和一黑一红的最长路径并不是在每棵红黑树上都存在的。假设该任意一条从根到NULL结点路径的长度为 h,那么有 bh <= h <= 2*bh

最长路径只能是最短路径的两倍


3. 红黑树的效率

  假设N是红黑树树中结点数量,h是最短路径的长度,那么有 2h−12^h - 12h1 <= N < 22∗h−12^{2*h} - 122h1,第一个下限是按照最短路径构成的满二叉树能出现的最大结点数,第二个上限是按照最长路径构成的满二叉树能出现的最大结点数。由此推出 h≈log⁡2Nh \approx \log_2 Nhlog2N,也就意味着红黑树增删查改最坏情况也是走最长路径 2h≈2∗log⁡2N2h \approx 2*\log_2 N2h2log2N,那么时间复杂度还是 O(log⁡2N)O(\log_2 N)O(log2N)

  红黑树的表达相对AVL树要抽象一些。AVL树通过高度差直观地控制了平衡,而红黑树通过5条规则的颜色约束,间接地实现了近似平衡,它们的效率都是同一档次的。相对而言,插入相同数量的结点,红黑树的旋转次数是更少的,因为它对平衡的控制那么严格。


4. 红黑树的实现

4.1 红黑树的结构

  • 使用枚举类型来枚举红色和黑色,保证代码的可读性。
  • 红黑树的结点采用 key/value 结构实现,内含三个结点指针,分别是左孩子、右孩子和父结点指针,同时包含一个枚举类型变量来表示结点的颜色。
// 枚举值表示颜色
enum Colour
{RED,BLACK
};// 这里默认按 key/value 结构实现
template <class K, class V>
struct RBTreeNode {// 这里更新控制平衡也要加入 parent指针pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Color _color;RBTreeNode (const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_color(Colour::RED){ }
};template <class K, class V>
class RBTree {typedef RBTreeNode<K, V> Node;
public:
private:Node* _root = nullptr;
};

4.2 红黑树的插入过程梳理

4.2.1 红黑树中插入一个值的大概过程

  1. 插入值按照二叉搜索树的规则进行插入,插入后观察树的目前状态是否符合红黑树的4条规则。
  2. 如果是空树插入,新增结点是黑色结点,将树的_root指针指向新增结点。
  3. 非空树插入后,新增结点必须是红色结点,如果父亲结点是黑色的,则没有违反任何规则,插入结束。
  4. 非空树插入后,新增结点必须是红色结点,如果父亲结点是红色的,则违反规则4(不红红)。需要根据三种情况来调整树的结构让树重新符合规则4。一次调整可能还解决不了问题,需要继续往上更新,往上更新也需要根据规则4和规则5继续判断。

有两点情况需要说明:

  • 后面的演示中,我们把新增结点标识为c(cur),c 的父亲标识为p(parent),p 的父亲标识为g(grandfather),p 的兄弟标识为u(uncle)。四个结点分别是新增结点、新增结点的父亲、新增结点的祖父和新增结点的叔叔。
  • 当新增结点违反规则4(不红红)时,其实c、p和g的颜色是固定的。新增结点c和新增结点的父结点p是红色,那么新增结点的祖父g一定是黑色。此时新增结点的叔叔u存在三种情况,根据这三种情况有对应的方法来调整树的结构来重新符合规则4。

4.2.2 uncle结点的三种情况

  根据上面的解释,可以根据规则4,知道c、p和g的颜色是固定的。因为根据规则4,连续的红色结点已经被处理掉,所以新出现的红色结点最多只能连续出现两个,不可能是三个。
uncle结点的三种情况
  根据上面的图片,读者可以自己想想怎么通过调色或者旋转+调色的方法来重新迎合规则4。

4.2.3 情况1:不需要进行任何操作

  c为红色,p为黑色时,不需要进行任何操作,因为没有违反规则。c 不仅可以是新插入的结点,也可以是向上更新过程中的结点。只要当 c 结点是红色时,p 结点是黑色就不需要再进行调色或者说是更新了。
情况1示意图

4.2.4 情况2:变色

  当 c 为红色结点,p 为红色结点,g 为黑色结点,u存在且为红色结点时,将 g 结点变红,pu 结点变黑,再把 g 当做新的 c,继续往上更新。

情况2示意图
上方这张图片简化了子树,只关注于这四个结点,真实场景中应该包含更多的子树,所以 c 结点可能是新插入的结点,也可能是调色后向上更新的 g 结点(g结点由黑变红后需要继续往上更新,g结点变为c结点)。

向上更新操作什么时候结束呢?

  1. 遇到情况1时,不需要进行操作时停止向上更新。
  2. 更新到根结点后,不能再继续往上更新了,就停止向上更新。

4.2.5 情况3:单旋+变色

  c 和 p 为红色,g 为黑色,u 不存在或者 u 存在且为黑时,c 和 p 在一条直线上,也就是符合 AVL树 中的LL或者RR类型,就需要用到 单旋+变色 来解决问题。根据LL或者RR类型分别使用右单旋或者左单旋方法。

步骤:1. 根据 c、p、g 的情况选择左单旋或者右单旋。 2. 旋转后,将 p 结点变成黑色结点,g 结点变成红色结点。

情况3示意图

情况3解决后,不需要再往上更新,因为子树的根结点更新为黑色结点,不会违反规则4,同时通过旋转后的调色,也保证了每条路径拥有的黑色结点个数不发生改变,符合规则5。

4.2.6 情况4: 双旋+变色

  c 和 p 为红色,g 为黑色,u 不存在或者 u 存在且为黑时,c 和 p 不在一条直线上,也就是符合 AVL树 中的LR或者RL类型,就需要用到 双旋+变色 来解决问题。根据LR或者RL类型分别使用左右双旋或者右左双旋方法。

步骤:1. 根据 c、p、g 的情况选择左右双旋或者右左双旋。 2. 旋转后,将 c 变成黑色结点,g 结点变成红色结点。

情况4示意图

情况4解决后,也不需要再往上更新,因为子树的根结点是黑色结点,不会违反规则4(不红红),同时通过旋转后的变色,也保证了每条路径拥有的黑色结点个数不发生改变,符合规则5。

4.3 红黑树的插入代码实现

首先,红黑树也是一棵二叉搜索树,先完成二叉搜索树的结构。

bool Insert(const pair<K, V>& kv) {if (_root == nullptr) {_root = new Node(kv);_root->_color = 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;}}// 退出while循环后,cur为空指针cur = new Node(kv);cur->_color = Colour::RED;// 判断 cur 是在 parent 的左边还是右边if (kv.first < parent->_kv.first) {parent->_left = cur;cur->_parent = parent;}else {parent->_right = cur;cur->_parent = parent;}// 到这里结点就插入成功了,后面就往上检查是否违反了规则,进行修正操作
}

紧接着就是完成向上检查是否违反了规则,进行修正操作。下面是完整的Insert函数:

bool Insert(const pair<K, V>& kv) {if (_root == nullptr) {_root = new Node(kv);_root->_color = 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;}}// 退出while循环后,cur为空指针cur = new Node(kv);cur->_color = Colour::RED;// 判断 cur 是在 parent 的左边还是右边if (kv.first < parent->_kv.first) {parent->_left = cur;cur->_parent = parent;}else {parent->_right = cur;cur->_parent = parent;}// 目前新结点是染的红色,现在向上判断是否需要 对结点重新染色// 如果 parent 结点为红色结点,则违反了规则4(不红红),需要进行修正操作while (parent && parent->_color == Colour::RED) {Node* grandfather = parent->_parent;Node* uncle = nullptr;// 根据 parent 结点的位置,来给 uncle 结点赋值if (parent == grandfather->_left) {uncle = grandfather->_right;}else {uncle = grandfather->_left;}// uncle 的三种情况,uncle 结点不存在,// uncle 结点存在,uncle 结点可能是红色,也可能是黑色// uncle 结点可能为空结点if (uncle != nullptr && uncle->_color == Colour::RED) {grandfather->_color = Colour::RED;uncle->_color = parent->_color = Colour::BLACK;// 继续向上传递cur = grandfather;parent = cur->_parent;}else if ((uncle == nullptr) || (uncle != nullptr && uncle->_color == Colour::BLACK)) {// uncle 结点是空结点 或者 uncle 结点的颜色为 黑色// 采用 旋转+调色,根据情况采用四种旋转方式后// 对 grandfather、parent 结点调色// LLif (grandfather->_left == parent && parent->_left == cur) {RotateR(grandfather);parent->_color = Colour::BLACK;grandfather->_color = Colour::RED;}// LRelse if (grandfather->_left == parent && parent->_right == cur) {RotateLR(grandfather);cur->_color = Colour::BLACK;grandfather->_color = Colour::RED;}// RRelse if (grandfather->_right == parent && parent->_right == cur) {RotateL(grandfather);parent->_color = Colour::BLACK;grandfather->_color = Colour::RED;}// RLelse if (grandfather->_right == parent && parent->_left == cur) {RotateRL(grandfather);cur->_color = Colour::BLACK;grandfather->_color = Colour::RED;}else {// 出现异常情况,不符合四种旋转情况assert(false);}}else {// 发生了异常情况,uncle结点存在且 颜色 非黑非红assert(false);}}// 默认 根结点为 黑色_root->_color = Colour::BLACK;return true;
}

代码有一句比较重要的代码,就是_root->_color = Colour::BLACK;这句代码保证了红黑树的根叶黑 。因为向上更新的while循环中,要保证 parent 不为空。但是根结点的 _parent 为空,所以当根结点因为向上调整而被染成红色后会导致根节点成为红色节点,此时需要进行特殊判断来恢复根结点的颜色为黑色。说是要特殊判断,也可以不用判断,一句代码强制每次插入都将根结点刷新成黑色也可以。

4.4 检查红黑树是否平衡

  使用规则4(不红红)和规则5(黑路同)来检查红黑树是否平衡。
  一开始需要先统计出一条路径中有多少个黑色结点。从根结点出发,一直遍历左孩子指针直到为空得到的这条路径可以作为一条参考路径。使用该路径作为标准来评判其他路径,若每条路径包含的黑色结点个数相同则为平衡,若有一条路径包含的黑色结点个数不相同则不平衡。
使用以下两个函数:

  1. bool IsBalanceTree()返回值为真,说明是红黑树;返回值为假,说明不是红黑树。
  2. bool Check(Node* root, int blackNum, int refNum);
    root是当前遍历到的结点。blackNum是当前遍历的这条路径中所拥有的黑色结点个数。refNum是参考的一条路径中应该包含的黑色结点个数。
    Check函数选择使用前序遍历,当 root 结点为空时,说明一条路径已经走完,此时判断 blackNum == refNum。若相等,则返回真;反之,则返回假。

为什么采用前序遍历得到一条路径?
这个不难想到,前序遍历从根节点出发,像是深度优先搜索一般,在没有到达尽头前会一直向下递归。而一旦到达尽头,其实也就是一条路径诞生了,这条路径就是我们要检查的东西。

Check函数就是我们的前序遍历函数,前序遍历产生路径,root为空时,blackNum就统计好了这条路径的黑色结点个数,而且blackNum是传值传参,不影响后续回溯时blackNum的值,而refNum是提前计算好的黑色结点个数,全程不会发生改变。同时检查结点为红色时,结点的父结点存在且要求不能也是红色结点。

下面是实现代码:

bool Check(Node* root, int blackNum, const int refNum) {if (root == nullptr) {// 前序遍历走到空时,意味着一条路径走完了// cout << blackNum << endl;// 规则5(黑路同)if (refNum != blackNum) {cout << "存在黑色结点的数量不相等的路径" << endl;return false;}return true;}// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了// 规则4(不红红)if (root->_color == Colour::RED && root->_parent && root->_parent->_color == Colour::RED) {// 违反了 不红红规则cout << root->_kv.first << "存在连续的红色结点" << endl;return false;}if (root->_color == Colour::BLACK) {blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);
}bool IsBalanceTree() {// 空树不是一棵红黑树,连根结点都没有,不能保证根结点为黑色结点if (_root == nullptr) {cout << "根结点为空,这是一棵空树" << endl;return false;}// 根结点一定要是黑色结点if (_root->_color == Colour::RED) {cout << "根结点为红色" << endl;return false;}// 参考值 通过遍历左孩子指针直到空来得到这条路径拥有的黑色结点数量int refNum = 0;Node* cur = _root;while (cur) {if (cur->_color == Colour::BLACK) {++refNum;}cur = cur->_left;}return Check(_root, 0, refNum);
}

5. 检查红黑树是否构建成功

  还是采用插入大量随机值,然后通过_IsBalance()函数来检验红黑树是否构建成功。

#include "RBTree.h"
#include <vector>int main() {RBTree<int, int> t;srand(time(0));int N = 10000000;vector<int> v;for (int i = 0; i < N; i++) {v.push_back((rand() + i));}for (int i = 0; i < N; i++) {t.Insert(make_pair(v[i], v[i]));}//t.InOrder();cout << endl;cout << "v[0] = " << v[0] << endl;cout << (t.IsBalanceTree() == 1 ? "这是一棵红黑树" : "这不是一棵红黑树") << endl;return 0;
}

检验红黑树构建成功


6. 附录(红黑树的代码)

/* RBTree.h */
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;// 枚举值表示颜色
enum Colour
{RED,BLACK
};// 这里默认按 key/value 结构实现
template <class K, class V>
struct RBTreeNode {// 这里更新控制平衡也要加入 parent指针pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _color;RBTreeNode (const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_color(Colour::RED){ }
};template<class K, class V>
class RBTree {typedef RBTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv) {if (_root == nullptr) {_root = new Node(kv);_root->_color = 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;}}// 退出while循环后,cur为空指针cur = new Node(kv);cur->_color = Colour::RED;// 判断 cur 是在 parent 的左边还是右边if (kv.first < parent->_kv.first) {parent->_left = cur;cur->_parent = parent;}else {parent->_right = cur;cur->_parent = parent;}// 目前新结点是染的红色,现在向上判断是否需要 对结点重新染色// 如果 parent 结点为红色结点,则违反了规则4(不红红),需要进行修正操作while (parent && parent->_color == Colour::RED) {Node* grandfather = parent->_parent;Node* uncle = nullptr;// 根据 parent 结点的位置,来给 uncle 结点赋值if (parent == grandfather->_left) {uncle = grandfather->_right;}else {uncle = grandfather->_left;}// uncle 的三种情况,uncle 结点不存在,// uncle 结点存在,uncle 结点可能是红色,也可能是黑色// uncle 结点可能为空结点if (uncle != nullptr && uncle->_color == Colour::RED) {grandfather->_color = Colour::RED;uncle->_color = parent->_color = Colour::BLACK;// 继续向上传递cur = grandfather;parent = cur->_parent;}else if ((uncle == nullptr) || (uncle != nullptr && uncle->_color == Colour::BLACK)) {// uncle 结点是空结点 或者 uncle 结点的颜色为 黑色// 采用 旋转+调色,根据情况采用四种旋转方式后// 对 grandfather、parent 结点调色// LLif (grandfather->_left == parent && parent->_left == cur) {RotateR(grandfather);parent->_color = Colour::BLACK;grandfather->_color = Colour::RED;}// LRelse if (grandfather->_left == parent && parent->_right == cur) {RotateLR(grandfather);cur->_color = Colour::BLACK;grandfather->_color = Colour::RED;}// RRelse if (grandfather->_right == parent && parent->_right == cur) {RotateL(grandfather);parent->_color = Colour::BLACK;grandfather->_color = Colour::RED;}// RLelse if (grandfather->_right == parent && parent->_left == cur) {RotateRL(grandfather);cur->_color = Colour::BLACK;grandfather->_color = Colour::RED;}else {// 出现异常情况,不符合四种旋转情况assert(false);}}else {// 发生了异常情况,uncle结点存在且 颜色 非黑非红assert(false);}}// 默认 根结点为 黑色_root->_color = Colour::BLACK;return true;}bool Find(const K& key) {Node* cur = _root;while (cur) {if (cur->_kv.first < key) {cur = cur->_right;}else if (cur->_kv.first > key) {cur = cur->_left;}else {return true;}}return false;}void InOrder() {_InOrder(_root);cout << endl;}bool Check(Node* root, int blackNum, const int refNum) {if (root == nullptr) {// 前序遍历走到空时,意味着一条路径走完了// cout << blackNum << endl;// 规则5(黑路同)if (refNum != blackNum) {cout << "存在黑色结点的数量不相等的路径" << endl;return false;}return true;}// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了// 规则4(不红红)if (root->_color == Colour::RED && root->_parent && root->_parent->_color == Colour::RED) {// 违反了 不红红规则cout << root->_kv.first << "存在连续的红色结点" << endl;return false;}if (root->_color == Colour::BLACK) {blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);}bool IsBalanceTree() {// 空树不是一棵红黑树,连根结点都没有,不能保证根结点为黑色结点if (_root == nullptr) {cout << "根结点为空,这是一棵空树" << endl;return false;}// 根结点一定要是黑色结点if (_root->_color == Colour::RED) {cout << "根结点为红色" << endl;return false;}// 参考值 通过遍历左孩子指针直到空来得到这条路径拥有的黑色结点数量int refNum = 0;Node* cur = _root;while (cur) {if (cur->_color == Colour::BLACK) {++refNum;}cur = cur->_left;}return Check(_root, 0, refNum);}
private:void RotateL(Node* cur) {	// 左单旋Node* parent = cur->_parent;Node* subR = cur->_right;Node* subRL = subR->_left;// 先操作 subR 和 subRL 之间的链接// 左单旋的目的,是让 subR 替代 cur 作为子树的根结点subR->_left = cur;cur->_parent = subR;cur->_right = subRL;	// sub 的左子树归 cur 的右孩子指针管// subRL 可以为空, 就是高度 h 为 0 的时候// 所以需要特判if (subRL != nullptr)subRL->_parent = cur;// 注意 cur 的父结点 parent 还链接着 cur,需要断开链接,让 parent的其中一个孩子指针指向 subR// 同时 parent 作为 cur 的父结点,当 cur 为根结点时,parent 为空,也需要特判if (parent == nullptr) {// 说明 cur 是根结点_root = subR;subR->_parent = nullptr;}else {// 说明 cur 不是根节点if (cur == parent->_left) {parent->_left = subR;}else {parent->_right = subR;}subR->_parent = parent;}}void RotateR(Node* cur) {	// 右单旋Node* parent = cur->_parent;// cur的父结点Node* subL = cur->_left;	// cur的左孩子Node* subLR = subL->_right;	// subL的右孩子// cur 成为 subL 的右孩子subL->_right = cur;cur->_parent = subL;// subLR 成为 cur 的左孩子cur->_left = subLR;if (subLR != nullptr)subLR->_parent = cur;if (parent == nullptr) {// 说明 cur 是根结点,parent 才为空结点_root = subL;subL->_parent = nullptr;}else {// parent 是非空结点, 但是不知道 cur 是 parent 的哪个孩子if (cur == parent->_left) {parent->_left = subL;}else {parent->_right = subL;}subL->_parent = parent;}}void RotateLR(Node* cur) {	// 左右双旋Node* subL = cur->_left;Node* subLR = subL->_right;RotateL(subL);RotateR(cur);}void RotateRL(Node* cur) {Node* subR = cur->_right;Node* subRL = subR->_left;RotateR(subR);RotateL(cur);}void _InOrder(Node* root) {if (root == nullptr) {return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}
private:Node* _root = nullptr;
};

希望对大家有所帮助! 😃

http://www.dtcms.com/a/394612.html

相关文章:

  • NumPy 系列(六):numpy 数组函数
  • 手写链路追踪-日志追踪性能分析
  • 数据库自增字段归零(id)从1开始累加
  • 轻量级本地化解决方案:实现填空题识别与答案分离的自动化流程
  • P1104 生日-普及-
  • CMake如何添加.C.H文件
  • 实时数据如何实现同步?一文讲清数据同步方式
  • 六、Java框架
  • 施耐德 M340 M580 数据移动指令 EXTRACT
  • 4. 引用的本质
  • 专业历史知识智能体系统设计与实现
  • 算法基础篇(4)枚举
  • 【C++】二叉搜索树及其模拟实现
  • 第二十一讲:C++异常
  • 2025年9月第2周AI资讯
  • 从 UNet 到 UCTransNet:一次分割项目中 BCE Loss 失效的踩坑记录
  • leetcode刷题记录2(java)
  • JAVA八股文——方法区
  • 链表操作与反转
  • AI编程 -- 学习笔记
  • 动态规划问题 -- 子数组模型(乘积最大数组)
  • 【AIGC】大模型面试高频考点18-大模型压力测试指标
  • Cannot find a valid baseurl for repo: base/7/x86_64
  • Lowpoly建模练习集
  • 六、kubernetes 1.29 之 Pod 控制器02
  • OpenCV:人脸检测,Haar 级联分类器原理
  • 类和对象 (上)
  • FreeRTOS 队列集(Queue Set)机制详解
  • 【论文速递】2025年第20周(May-11-17)(Robotics/Embodied AI/LLM)
  • 【秋招笔试】2025.09.21网易秋招笔试真题