《数据结构、算法与应用C++语言描述》-红黑树的C++实现-百万级数据量测试通过

红黑树

完整可编译运行代码见仓库:GitHub - Jasmine-up/Data-Structures-Algorithms-and-Applications/_35Red black tree。
如有问题请在评论区指出。另外,Github仓库会根据我的学习情况持续更新,欢迎大家点star,谢谢。

基本概念

红-黑树(red-black tree):树中每一个节点的颜色或者是黑色或者是红色。每一个空指针用一个外部节点代替。红黑树是一种二叉搜索树。

基于节点特点的等价

  • RB1:根节点和所有外部节点都是黑色。
  • RB2:在根至外部节点路径上,没有连续两个节点是红色。
  • RB3:在所有根至外部节点的路径上,黑色节点的数目都相同。

基于指针颜色的等价:从父节点指向黑色孩子的指针是黑色的,从父节点指向红色孩子的指针是红色的。

  • RB1’:从内部节点指向外部节点的指针是黑色的。
  • RB2’:在根至外部节点路径上,没有两个连续的红色指针。
  • RB3’:在所有根至外部节点路径上,黑色指针的数目都相同。

注意,如果知道指针的颜色,就能推断节点的颜色,反之亦然。

举例

在图15-10的红-黑树中,阴影的方块是外部节点,阴影的圆圈是黑色节点,白色圆圈是红色节点,粗线是黑色指针,细线是红色指针。注意,在每一条根至外部节点的路径上,都有两个黑色指针和三个黑色节点(包括根节点和外部节点),且不存在两个连续的红色节点或指针。

在这里插入图片描述

节点的阶:是从该节点到一外部节点的路径上黑色指针的数量,外部节点的阶是0。

定理

定理15-1设从根到外部节点的路径长度(length)是该路径上的指针数量。如果P和 Q是红-黑树中的两条从根至外部节点的路径,那么 l e n g t h ( P ) ≤ 2 l e n g t h ( Q ) length(P)\leq 2length(Q) length(P)2length(Q)

证明:在红黑树中,指向外部节点的指针是黑色,没有两个连续的红色指针,但是可以有连续的黑色指针,如果一条路径都是黑色指针,另一条路径是红色黑色指针交叉,这样就可得到极限情况,也就是 l e n g t h ( P ) = 2 l e n g t h ( Q ) length(P)=2length(Q) length(P)=2length(Q)

定理15-2令h是一棵红-黑树的高度(不包括外部节点),n是树的内部节点数量,而 r是根节点的阶,则

  • 1) h ≤ 2 r h \leq 2r h2r
  • 2) n ≥ 2 r − 1 n\geq 2^r-1 n2r1
  • 3) h ≤ 2 l o g 2 ( n + 1 ) h\leq 2log_2(n+1) h2log2(n+1)

红黑树的描述

外部节点使用空指针描述。对于每个节点,需要存储左孩子、右孩子、父亲节点的指针,对于颜色可以存储该节点的颜色或指向它的两个孩子的指针颜色。存储每个节点的颜色只需要附加一位,而存储每个指针的颜色则需要两位。选择哪种方案可根据实际需求决定。

搜索

假设要查找关键字为theKey的元素。先从根开始查找。如果根为空,那么搜索树不包含任何元素,即查找失败。如果不空,则将 theKey与根的关键字相比较。如果 theKey小,那么就不必在右子树中查找,只要查找左子树。如果theKey大,则正好相反,只需查找右子树。如果 theKey等于根的关键字,则查找成功。时间复杂度为O(logn)。

这个和二叉搜索树的查找是一样的。

AVL树是高度最小的,因此,在以搜索操作为主的应用中,在最坏情况下AVL树的时间复杂度是最优的。

插入

以下思路是普通二叉搜索树的插入方法,红黑树需要调整是否平衡。

假设要在二叉搜索树中插人一个新元素thePair,首先要通过查找来确定,在树中是否存在某个元素,其关键字与thePair.first 相同。如果搜索成功,那么就用 thePair.second 替代该元素的值。如果搜索不成功,那么就在搜索中能找到键值与thePair.first相近的节点pp,当thePair.second < pp.second时,将thePair作为pp的左孩子;反之,将thePair作为pp的右孩子。在插入元素后,treeSize++。

对插入的新元素,需要上色。如果插人前的树是空的,那么新节点是根节点,颜色应是黑色(参看特征RB1)。假设插入前的树是非空的。

如果新节点的颜色是黑色,那么在从根到外部节点的路径上,将有一个特殊的黑色节点作为新节点的孩子。如果新节点是红色,那么可能出现两个连续的红色节点。把新节点赋为黑色将肯定不符合RB3,而把新节点赋为红色则可能违反,也可能符合 RB2,因此,应将新节点赋为红色。

如果将新节点赋为红色而引起特征 RB2的破坏,我们就说树的平衡被破坏了。不平衡的类型可以通过检查新节点u、其父节点pu及祖父节点gu来确定。RB2被破坏后的情况便是有两个连续的红色节点:一个是u,另一个一定是它的父节点,因此pu存在。因为pu是红色的,它不可能是根(根据特征RB1),所以u必定有一个祖父节点gu,并且是黑色的(根据特征RB2)。

LLb类型不平衡:当pu是gu的左孩子,u是pu的左孩子且gu的另一个孩子是黑色时(这另一个可以是外部节点)。

LLr类型不平衡:当pu是gu的左孩子,u是pu的左孩子且gu的另一个孩子是红色时(这另一个可以是外部节点)。

LRb类型不平衡:当pu是gu的左孩子,u是pu的右孩子且gu的另一个孩子是黑色时(这另一个可以是外部节点)。

LRr类型不平衡:当pu是gu的左孩子,u是pu的右孩子且gu的另一个孩子是红色时(这另一个可以是外部节点)。

RRb类型不平衡:当pu是gu的右孩子,u是pu的右孩子且gu的另一个孩子是黑色时(这另一个可以是外部节点)。

RRr类型不平衡:当pu是gu的右孩子,u是pu的右孩子且gu的另一个孩子是红色时(这另一个可以是外部节点)。

RLb类型不平衡:当pu是gu的右孩子,u是pu的左孩子且gu的另一个孩子是黑色时(这另一个可以是外部节点)。

RLr类型不平衡:当pu是gu的右孩子,u是pu的左孩子且gu的另一个孩子是红色时(这另一个可以是外部节点)。

XYr(X和Y既可以是L,也可以是R)类型的不平衡可以通过改变颜色来处理,而 XYb类型则需要旋转。当节点gu被改变颜色时,它和上一层的节点可能破坏了RB2特性。这时的不平衡需要重新分类,而且u变为gu,然后再次进行转换。旋转结束后,不再违反RB2特性,因此不需要再进行其他操作。

举例:黑色节点用阴影表示,红色节点没有阴影。加粗指针是黑色指针,较细指针是红色指针。

LLr型与LRr型不平衡调整:例如,在图15-11a中,gu是黑色,而pu和u是红色。gu的两个指针是红色。 g u R gu_R guR是gu的右子树, p u R pu_R puR是pu的右子树。**LLr 和 LRr颜色调整需要将pu和 gu的右孩子由红色改为黑色。另外,如果gu不是根,还要将gu的颜色由黑色改为红色。如果gu是根节点,那么颜色不变,这时,所有从根至外部节点路径上的黑色节点数量都增1。**如果将 gu的颜色改为红色而引起了不平衡,那么gu就变成了新的u节点,它的双亲就变成了新的pu,它的祖父节点就变成了新的gu,这时需要继续恢复平衡。如果gu是根节点或者gu节点的颜色改变没有违反规则RB2,那么工作就完成了。

在这里插入图片描述

LLb型和LRb型不平衡调整:图15-12是处理LLb 和 LRb不平衡时所做的旋转。在图15-12a 和图15-12b中,pu是 p u L pu_L puL的根。注意,这些旋转与AVL树的插人操作所需要的LL(见图15-4)和LR(见图15-5所示)旋转有相似之处。指针的改变是相同的,但是,在LLb旋转中,不仅要改变指针,还要将gu的颜色由黑色改为红色,将pu的颜色由红色改为黑色。在LRb旋转中,需要将u的颜色由红色改为黑色,将gu的颜色由黑色改为红色。

在这里插入图片描述
在这里插入图片描述

删除

使用普通二叉搜索树的删除元素方法,三种情况:

1)p是树叶;

要删除的节点是叶节点。存储叶节点到temp,将叶节点的父节点的左孩子置为nullptr,释放叶节点temp空间。若是根节点,直接释放根节点空间,令根为 nullptr。

2)p只有一棵非空子树;

要删除的节点p只有一棵子树。如果p没有父节点(即p是根节点),则p的唯一子树的根节点成为新的搜索树的根节点。如果p有父节点pp,则修改pp的指针域,使得它指向p的唯一孩子,然后释放节点p。

3)p有两棵非空子树。

先将该节点的元素替换为它的左子树的最大元素或右子树的最小元素,然后把替换元素的节点删除。本文使用左子树的最大元素替换。

要在一个节点的左子树中查找关键字最大的元素,先移动到左子树的根,然后沿着右孩子指针移动,直到右孩子指针为nullptr的节点为止。类似地,要在一个节点的右子树中查找关键字最小的元素,先移动到右子树的根,然后沿着左孩子指针移动,直到左孩子指针为nullptr的节点为止。

调整红黑树

在红黑树中,如果删除的节点是红色节点,那么删除节点后依然满足红黑树特性,不需要重新调整平衡;如果删除的节点是黑色节点,则可能会出现违反RB3的情况。令y是替代被删除节点的节点,也就是y被删除节点的父节点新指向的节点。

当违反 RB3的情况出现时,以y为根的子树缺少一个黑色节点(或一个黑色指针),因此,从根至y子树的外部节点的路径与从根至其他外部节点的路径相比,前者所包含的黑色节点数量比后者的要少一个,这时的树是不平衡的。

不平衡的类型可以根据y的父节点py和兄弟节点v的特点来划分。当y是py的右孩子时,不平衡是R型的,否则是L型的。通过观察可以得知,如果y缺少一个黑色节点,那么v就肯定不是外部节点。如果v是一个黑色节点,那么不平衡是Lb或Rb型的;而当v是红色节点时,不平衡是Lr或Rr型的。首先考察Rb型的不平衡。Lb型不平衡的处理与之相似。根据v的红色孩子的数量,把Rb型不平衡细分为三种情况:Rb0、Rb1和Rb2。

情况一:y是py的右孩子时且v是黑色节点时,不平衡类型为Rb型,可以根据v的红色孩子数量分为三种情况:Rb0、Rb1和Rb2。

如图15-15,是Rb0型的不平衡,如果py在改变前是黑色的,那么颜色的改变将导致以py为根的子树缺少一个黑色节点。在图15-15b中,从根至v的外部节点路径上,黑色节点数量也减少了一个。因此,颜色改变后,无论是从根到v的外部节点的路径,还是从根到y的外部节点的路径,都会缺少一个黑色节点。如果py是整棵红-黑树的根,那么就不需要再做其他工作,否则,py就成新的y,y的不平衡需要重新划分,并且在新的y点需要再进行调整。

若改变颜色前py是红色,则从根到y的外部节点的路径上,黑色节点数量增加了一个,而从根到v的外部节点的路径上,黑色节点数量没有改变。整棵树达到平衡。

在这里插入图片描述

当不平衡类型是Rb1和Rb2时,需要进行旋转,如图15-16所示。在图中,带阴影的节点既可能是红色,也可能是黑色。这种节点的颜色在旋转后不会发生变化。因此,图15-16b中,子树的根在旋转前和旋转后,颜色保持不变——图15-16b中v的颜色与图15-16a 中py的颜色是一样的。可以证明,在旋转后,从根至y的外部节点的路径上,黑色节点(黑色指针)数量增加一个,而从根至其他外部节点路径上,黑色节点的数量没有变化。旋转使树恢复了平衡。

在这里插入图片描述

情况二:y是py的右孩子时且v是红色节点时,不平衡类型为Rr型,可以根据v的右孩子的红色孩子数量分为三种情况:Rr0、Rr1和Rr2。都可以通过一次旋转获得平衡。

由于y中缺少一个黑色节点并且v节点是红色,所以 v L v_L vL v R v_R vR都至少有一个黑色节点不是外部节点,因此,v的孩子都是内部节点。以下是这三种情况的旋转方法。

在这里插入图片描述
在这里插入图片描述

情况三:y是py的左孩子时且v是黑色节点时,不平衡类型为Lb型,可以根据v的红色孩子数量分为三种情况:Lb0、Lb1和Lb2。

Lb型的解决方案与Rb型的解决方案是基于y节点的父亲节点的完全镜像。

情况四:y是py的左孩子时且v是红色节点时,不平衡类型为Lr型,可以根据v的红色孩子数量分为三种情况:Lr0、Lr1和Lr2。

Lr型的解决方案与Rr型的解决方案是基于y节点的父亲节点的完全镜像。

代码

main.cpp

/*
Project name :			_35Red_black_tree
Last modified Date:		2024年1月5日21点00分
Last Version:			V1.0
Descriptions:			main()主函数
*/
#include "RedBlackTree.h"int main() {RedBlackTreeTest();return 0;
}

RedBlackTree.h

/*
Project name :			_35Red_black_tree
Last modified Date:		2024年1月6日18点17分
Last Version:			V1.0
Descriptions:			红黑树模板类
*/#ifndef _35RED_BLACK_TREE_REDBLACKTREE_H
#define _35RED_BLACK_TREE_REDBLACKTREE_H#include "RedBlackTreeNode.h"
#include "dictionary.h"
#include <stack>void RedBlackTreeTest();using namespace std;template<class K, class E>
class RedBlackTree : public dictionary<K, E> {
public:RedBlackTree() {root = nullptr;treeSize = 0;}[[nodiscard]] bool empty() const { return treeSize == 0; }[[nodiscard]] int size() const { return treeSize; }pair<K, E> *find(K theKey) const;void insert(pair<K, E> &thePair);void erase(K theKey);/*中序遍历二叉树,使用函数指针的目的是是的本函数可以实现多种目的*/void inOrder(void(*theVisit)(RedBlackTreeNode<pair<K, E>> *)) {visit = theVisit;/*是因为递归,所以才要这样的*/inOrder(root);/*这里调用的是静态成员函数inOrder()*/}/*中序遍历---输出endl*/void inOrderOutput() {inOrder(output);cout << endl;}/*前序遍历二叉树,使用函数指针的目的是是的本函数可以实现多种目的*/void preOrder(void(*theVisit)(RedBlackTreeNode<pair<K, E>> *)) {visit = theVisit;int num = 0;/*是因为递归,所以才要这样的*/preOrder(root, num);/*这里调用的是静态成员函数preOrder()*/cout << "num = " << num << endl;}/*中序遍历---输出endl*/void preOrderOutput() {preOrder(output);cout << endl;}bool ISRBTree();private:RedBlackTreeNode<pair<K, E>> *root;// 指向根的指针int treeSize;// 树的结点个数static void (*visit)(RedBlackTreeNode<pair<K, E>> *);//是一个函数指针,返回值为void 函数参数为binaryTreeNode<pair<K, E>>*static void output(RedBlackTreeNode<pair<K, E>> *t) { cout << *t << endl; }static void inOrder(RedBlackTreeNode<pair<K, E>> *t);static void preOrder(RedBlackTreeNode<pair<K, E>> *t, int& num);void rotateLL(RedBlackTreeNode<pair<K, E>> *&x);void rotateLR(RedBlackTreeNode<pair<K, E>> *&x);void rotateRR(RedBlackTreeNode<pair<K, E>> *&x);void rotateRL(RedBlackTreeNode<pair<K, E>> *&x);void rotateRr1_2and2(RedBlackTreeNode<pair<K, E>> *&pp);void rotateLr1_2and2(RedBlackTreeNode<pair<K, E>> *&pp);bool _ISRBTree(RedBlackTreeNode<pair<K, E> > * root, int count, int BlackCount);
};/*私有静态成员初始化*/
/*这里是静态函数指针成员的初始化,不初始化会引发LINK错误*/
template<class K, class E>
void (*RedBlackTree<K, E>::visit)(RedBlackTreeNode<pair<K, E>> *) = 0;      // visit function/*中序遍历 递归*/
template<class K, class E>
void RedBlackTree<K, E>::inOrder(RedBlackTreeNode<pair<K, E>> *t) {if (t != nullptr) {inOrder(t->leftChild);/*中序遍历左子树*/visit(t);/*访问树根*/inOrder(t->rightChild);/*中序遍历右子树*/}
}/*前序遍历 递归*/
template<class K, class E>
void RedBlackTree<K, E>::preOrder(RedBlackTreeNode<pair<K, E>> *t, int& num) {if (t != nullptr) {visit(t);/*访问树根*/num++;preOrder(t->leftChild, num);/*中序遍历左子树*/preOrder(t->rightChild, num);/*中序遍历右子树*/}
}/*  查找元素*  输入:theKey表示需要查找元素的键值*  输出:键值为theKey的节点的pair地址*  时间复杂度:O(logn),n表示节点个数*/
template<class K, class E>
pair<K, E> *RedBlackTree<K, E>::find(K theKey) const {// 返回值是匹配数对的指针// 如果没有匹配的数对,返回值为nullptr// p从根节点开始搜索,寻找关键字等于theKey的一个元素RedBlackTreeNode<pair<K, E> > *p = root;while (p != nullptr)// 检查元素 p->elementif (theKey < p->element.first)p = p->leftChild;else if (theKey > p->element.first)p = p->rightChild;else // 找到匹配的元素return &p->element;// 没找到匹配的元素return nullptr;
}/**  LL旋转*  输入:x是第一个L的父亲节点,插入元素和删除元素都会用到*  输出:void*  时间复杂度:O(1)*  注意事项:执行本函数前后x指向的元素会改变为新的该子树的根节点*/
template<class K, class E>
void RedBlackTree<K, E>::rotateLL(RedBlackTreeNode<pair<K, E>> *&x) {// 记录祖父节点的父亲节点RedBlackTreeNode<pair<K, E>> *Parent = x->parent;RedBlackTreeNode<pair<K, E>> *b = x->leftChild;x->leftChild = b->rightChild;if(b->rightChild)b->rightChild->parent = x;b->rightChild = x;// x的父亲节点变为bx->parent = b;// b的父亲节点变为原来x的父亲节点b->parent = Parent;// 这里就是原来祖父节点的父亲现在需要作为b的父亲,前提是祖父节点的父亲存在if (Parent != nullptr) {if (x == Parent->leftChild)Parent->leftChild = b;elseParent->rightChild = b;} elseroot = b;// 祖父节点如果没有父亲的话就是根节点x = b;// b节点将替换x节点
}/**  RR旋转*  输入:x表示第一个R的父亲节点,在插入和删除时都会用到*  输出:void*  时间复杂度:O(1)*  注意事项:执行本函数前后x指向的元素会改变为新的该子树的根节点*/
template<class K, class E>
void RedBlackTree<K, E>::rotateRR(RedBlackTreeNode<pair<K, E>> *&x) {// 记录祖父节点的父亲节点RedBlackTreeNode<pair<K, E>> *Parent = x->parent;RedBlackTreeNode<pair<K, E>> *b = x->rightChild;x->rightChild = b->leftChild;if(b->leftChild)b->leftChild->parent = x;b->leftChild = x;x->parent = b;// x的父亲节点为b// b的父亲节点为祖父节点的父亲节点b->parent = Parent;// 这里就是原来祖父节点的父亲现在需要作为b的父亲,前提是祖父节点的父亲存在if (Parent != nullptr) {if (x == Parent->leftChild)Parent->leftChild = b;elseParent->rightChild = b;} elseroot = b;// 祖父节点如果没有父亲的话就是根节点x = b;
}/**  LR旋转*  输入:x表示L的父亲节点,插入元素和删除元素时都会用到*  输出:void*  时间复杂度:O(1)*  注意事项:执行本函数前后x指向的元素会改变为新的该子树的根节点*/
template<class K, class E>
void RedBlackTree<K, E>::rotateLR(RedBlackTreeNode<pair<K, E>> *&x) {rotateRR(x->leftChild);rotateLL(x);
}/**  RL旋转*  输入:x表示R的父亲节点,插入元素和删除元素都会用到*  输出:void*  时间复杂度:O(1)*  注意事项:执行本函数前后x指向的元素会改变为新的该子树的根节点*/
template<class K, class E>
void RedBlackTree<K, E>::rotateRL(RedBlackTreeNode<pair<K, E>> *&x) {rotateLL(x->rightChild);rotateRR(x);
}/** 删除节点时:对于Rr1(2)型和Rr2型的旋转使用* 输入:替换被删除节点的y节点的父亲节点* 输出:void* 时间复杂度:O(1)*/
template<class K, class E>
void RedBlackTree<K, E>::rotateRr1_2and2(RedBlackTreeNode<pair<K, E>> *&pp) {RedBlackTreeNode<pair<K, E>> *Parent = pp->parent;RedBlackTreeNode<pair<K, E>> *w = pp->leftChild->rightChild;RedBlackTreeNode<pair<K, E>> *x = w->rightChild;if(x != nullptr){w->rightChild = x->leftChild;if(x->leftChild)x->leftChild->parent = w;// 父亲节点要及时更改}else{cout << "rotateRr1_2and2 error!" << endl;}x->leftChild = pp->leftChild;pp->leftChild->parent = x;pp->leftChild = x->rightChild;if(x->rightChild)x->rightChild->parent = pp;x->rightChild = pp;pp->parent = x;if(Parent->leftChild == pp)Parent->leftChild = x;elseParent->rightChild = x;x->parent = Parent;pp = x;
}/** 删除节点时,对于Lr1(2)型和Lr2型的旋转使用* 输入:替换被删除节点的y节点的父亲节点* 输出:void* 时间复杂度:O(1)*/
template<class K, class E>
void RedBlackTree<K, E>::rotateLr1_2and2(RedBlackTreeNode<pair<K, E>> *&pp) {RedBlackTreeNode<pair<K, E>> *Parent = pp->parent;RedBlackTreeNode<pair<K, E>> *w = pp->rightChild->leftChild;RedBlackTreeNode<pair<K, E>> *x = w->leftChild;if(x != nullptr){w->leftChild = x->rightChild;if(x->rightChild)x->rightChild->parent = w;// 父亲节点要及时更改}else{cout << "rotateLr1_2and2 error!" << endl;}x->rightChild = pp->rightChild;pp->rightChild->parent = x;pp->rightChild = x->leftChild;if(x->leftChild)x->leftChild->parent = pp;x->leftChild = pp;pp->parent = x;if(Parent->leftChild == pp)Parent->leftChild = x;elseParent->rightChild = x;x->parent = Parent;pp = x;
}/**  插入元素*  输入:const pair<K, E> thePair表示需要插入的键值对*  输出:void*  时间复杂度:O(logn),表示节点个数*/
template<class K, class E>
void RedBlackTree<K, E>::insert(pair<K, E> &thePair) {// 当红黑树为空时,插入节点直接作为根节点,且其颜色为黑色if (root == nullptr) {root = new RedBlackTreeNode<pair<K, E> >(thePair, nullptr, nullptr, nullptr, false);treeSize++;return;}// 如果树非空. 如果该键值存在则覆盖元素// 寻找插入位置RedBlackTreeNode<pair<K, E> > *p = root,*pp = nullptr;while (p != nullptr) {// 检查元素 p->elementpp = p;// 如果当前键值小于p的键值,则移到p的左孩子if (thePair.first < p->element.first)p = p->leftChild;else// 如果当前键值大于p的键值,则移到p的右孩子if (thePair.first > p->element.first)p = p->rightChild;else {// 如果键值相等,覆盖旧的值p->element.second = thePair.second;return;}}// 为thePair建立一个节点,然后与pp链接,此时pp是叶子节,默认插入为红色节点auto *newNode = new RedBlackTreeNode<pair<K, E> >(thePair);// 如果thePair的键值小于pp的键值,则将thePair作为pp的左孩子,反之将其作为右孩子if (thePair.first < pp->element.first) {pp->leftChild = newNode;newNode->parent = pp;} else {pp->rightChild = newNode;newNode->parent = pp;}treeSize++;p = newNode;// 如果父亲节点存在且父亲节点是红色的,则需要对红黑树进行调整while (pp && pp->isRed == true) {// parent是红色,则其父节点一定存在RedBlackTreeNode<pair<K, E> > *grandfather = pp->parent;if (pp == grandfather->leftChild) {// parent是grandfather的左孩子// LYr(Y可以是L或R)的情况RedBlackTreeNode<pair<K, E> > *uncle = grandfather->rightChild;// 找到grandfather的右孩子,可以称为叔叔if (uncle && uncle->isRed == true) {// 如果叔叔存在且为红色// 将父亲节点和其叔叔节点的颜色更改为黑色pp->isRed = uncle->isRed = false;// 只有在祖父节点不为根节点时,将祖父节点的颜色更改为红色if (grandfather != root)grandfather->isRed = true;else // 如果祖父节点已经是根节点了,那么就直接终止循环,因为不可能再向上处理了break;// 继续往上处理p = grandfather;pp = p->parent;} else {// uncle不存在或者uncle存在且为黑色// 如果当前节点是其父亲节点的左孩子// LLb的情况:左孩子的左孩子if (p == pp->leftChild) {rotateLL(grandfather);// LL单旋// 调整颜色// 现在的grandfather指向的是原来的父亲节点,grandfather的右孩子指向的是原祖父节点grandfather->isRed = false;grandfather->rightChild->isRed = true;} else {// LRb的情况rotateLR(grandfather);// 调整颜色// 现在的grandfather指向的是原来的新节点u,u的左孩子是原来的父亲节点pu,u的右孩子是原来的祖父节点gugrandfather->isRed = false;grandfather->rightChild->isRed = true;}break;// 无需继续向上进行处理}} else {// parent是grandfather的右孩子// RYr(Y可以是L或R)的情况RedBlackTreeNode<pair<K, E> > *uncle = grandfather->leftChild;// 找到grandfather的左孩子,可以称为叔叔if (uncle && uncle->isRed == true) {// 如果叔叔存在且为红色// 将父亲节点和其叔叔节点的颜色更改为黑色pp->isRed = uncle->isRed = false;// 只有在祖父节点不为根节点时,将祖父节点的颜色更改为红色if (grandfather != root)grandfather->isRed = true;else // 如果祖父节点已经是根节点了,那么就直接终止循环,因为不可能再向上处理了break;// 继续往上处理p = grandfather;pp = p->parent;} else {// uncle不存在或者uncle存在且为黑色// 如果当前节点是其父亲节点的右孩子// RRb的情况:右孩子的右孩子if (p == pp->rightChild) {rotateRR(grandfather);// RR单旋// 调整颜色// 现在的grandfather指向的是原来的父亲节点,grandfather的左孩子指向的是原祖父节点grandfather->isRed = false;grandfather->leftChild->isRed = true;} else {// LRb的情况rotateRL(grandfather);// 调整颜色// 现在的grandfather指向的是原来的新节点u,u的右孩子是原来的父亲节点pu,u的左孩子是原来的祖父节点gugrandfather->isRed = false;grandfather->leftChild->isRed = true;}break;// 无需继续向上进行处理}}}
}/**  删除元素*  输入:const K theKey表示需要删除元素的键值*  输出:void*  时间复杂度:O(logn),n表示节点个数*/
template<class K, class E>
void RedBlackTree<K, E>::erase(K theKey) {// 删除关键字等于theKey的数对// 查找关键字为theKey的节点RedBlackTreeNode<pair<K, E> > *p = root,*pp = nullptr;while (p != nullptr && p->element.first != theKey){pp = p;if (theKey < p->element.first)p = p->leftChild;elsep = p->rightChild;}if (p == nullptr){cout << theKey << " not exist!" << endl;return; // 不存在与关键字theKey匹配的数对}// 重新组织树结构// 当p有两个孩子时的处理if (p->leftChild != nullptr && p->rightChild != nullptr){// 两个孩子// 在P的左子树中寻找最大元素RedBlackTreeNode<pair<K, E> > *s = p->leftChild,*ps = p;  // s的父节点while (s->rightChild != nullptr){// 移动到更大的pairps = s;s = s->rightChild;// 右孩子比较大}// 将最大元素s移到p// p->element = s->element 的键值是 const// 当最大值就是p的左孩子时,new的元素不能直接指向p的左孩子,而要指向p的左孩子的左孩子(此时p的左孩子没有右孩子),因为到时候s会被delete掉,这个问题在后面的p至多有一个孩子那里解决的RedBlackTreeNode<pair<K, E> >* q = nullptr;// 值用s的值替换,颜色和其他指针的指向都是p的q = new RedBlackTreeNode<pair<K, E> >(s->element, p->leftChild, p->rightChild, p->parent, p->isRed);if(p->leftChild)p->leftChild->parent = q;if(p->rightChild)p->rightChild->parent = q;// pp是p的父节点// 如果p没有父节点if (pp == nullptr)root = q;else if (p == pp->leftChild)// 如果p是pp的左孩子pp->leftChild = q;else// 如果p是pp的右孩子pp->rightChild = q;// 如果s的父节点就是p,说明p节点的左子树只有左子树没有右子树// 那么删除p后pp就是其父节点if (ps == p) pp = q;else pp = ps;// 反之ps是其父节点delete p;p = s;}// p至多只有一个孩子// 把孩子的指针存放到cRedBlackTreeNode<pair<K, E> > *c;if (p->leftChild != nullptr)c = p->leftChild;elsec = p->rightChild;// 删除pbool isLeft = false;// 记录p节点是左孩子还是右孩子if (p == root)root = c;else{// p是pp的左孩子还是右孩子if (p == pp->leftChild){pp->leftChild = c;// 更新一下c节点的父亲节点if(c != nullptr)c->parent = pp;isLeft = true;}else{pp->rightChild = c;// 更新一下c节点的父亲节点if(c != nullptr)c->parent = pp;}}treeSize--;// 之前就找到了y节点的父亲节点pp// 如果被删除节点是红色的,则红黑树还是平衡的;如果被删除节点是黑色的,则红黑树需要调整if(p->isRed == false){// 父亲节点不为空,说明还需要继续调整while(pp){// 如果c节点不为空,则需要判断c节点是左孩子还是右孩子// 这个主要是用于第二轮调整平衡,也就是c节点此时不再是p的孩子节点if(c){if(c == pp->leftChild)isLeft = true;elseisLeft = false;}if(isLeft){// 找到被删除节点的兄弟RedBlackTreeNode<pair<K, E> > *v = pp->rightChild;// 如果兄弟节点是黑色节点 Lb型// 如果c和v都为空的话,那么不用调整平衡if(!c && !v)break;if(!v->isRed){// 如果v的左右孩子都是红色节点,Lb2型;或者v的左孩子是红色节点,Lb1(2)型if(v->leftChild && v->leftChild->isRed){rotateRL(pp);// OK1pp->isRed = pp->leftChild->isRed;pp->leftChild->isRed = false;break;}else if(v->rightChild && v->rightChild->isRed){// 如果v的左孩子是红色节点,Lb1(1)型rotateRR(pp);// OK1pp->isRed = pp->leftChild->isRed;// 这个节点的颜色不变pp->leftChild->isRed = false;// 原来的祖先节点的颜色变为黑色节点v->rightChild->isRed = false;// 将v的左孩子更改为黑色节点break;// 不需要继续调整了}else{// 如果v的左右孩子都是黑色节点,Lb0型// pp是红色节点,改变pp为黑色节点,改变v为红色节点if(pp->isRed){pp->isRed = false;// OK1v->isRed = true;break;// 终止循环,不需要再继续调整了}else{// pp是黑色节点,将v节点改变为红色节点,然后将pp节点作为新的y节点,也就是程序中的p节点v->isRed = true;//OK1// 继续调整平衡c = pp;pp = c->parent;}}}else{// 如果兄弟节点是红色节点 Lr型,则兄弟节点一定有左右孩子// 找到被删除节点的兄弟的左孩子RedBlackTreeNode<pair<K, E> > *vL = v->leftChild;// 如果vL的左右孩子都是红色节点,Lr2型 或者 如果v的左孩子是红色节点,Lr1(2)型if(vL && vL->leftChild && vL->leftChild->isRed){rotateLr1_2and2(pp);// OK1pp->isRed = false;break;}else if(vL && vL->rightChild && vL->rightChild->isRed){// 如果v的右孩子是红色节点,Lr1(1)型rotateRL(pp);// OK1if(vL->rightChild->leftChild)vL->rightChild->leftChild->isRed = false;break;}else{// 如果v的左右孩子都是黑色节点,Lr0型rotateRR(pp);// OK1pp->isRed = false;// 更改原来的v节点为黑色节点if(pp->leftChild->rightChild)pp->leftChild->rightChild->isRed = true;break;}}}else{// 否则是R型// 找到被删除节点的兄弟RedBlackTreeNode<pair<K, E> > *v = pp->leftChild;// 如果c和v都为空的话,那么树本身就是平衡的if((!c && !v))break;// 如果兄弟节点是黑色节点 Rb型if(!v->isRed){// 如果v的左右孩子都是红色节点,Rb2型;或者v的右孩子是红色节点,Rb1(2)型if(v->rightChild && v->rightChild->isRed){rotateLR(pp);// OK1pp->isRed = pp->rightChild->isRed;pp->rightChild->isRed = false;break;}else if(v->leftChild && v->leftChild->isRed){// 如果v的左孩子是红色节点,Rb1(1)型rotateLL(pp);// OK1pp->isRed = pp->rightChild->isRed;// 这个节点的颜色不变pp->rightChild->isRed = false;// 原来的祖先节点的颜色变为黑色节点v->leftChild->isRed = false;// 将v的左孩子更改为黑色节点break;// 不需要继续调整了}else{// 如果v的左右孩子都是黑色节点,Rb0型// pp是红色节点,改变pp为黑色节点,改变v为红色节点if(pp->isRed){pp->isRed = false;// OK1v->isRed = true;break;// 终止循环,不需要再继续调整了}else{// pp是黑色节点,将v节点改变为红色节点,然后将pp节点作为新的y节点,也就是程序中的p节点// 这个没有找到合适的节点去测试v->isRed = true;//OK1c = pp;pp = c->parent;}}}else{// 如果兄弟节点v是红色节点 Rr型,则兄弟节点v一定有左右孩子// 找到被删除节点的兄弟的右孩子RedBlackTreeNode<pair<K, E> > *vR = v->rightChild;// 如果vR的左右孩子都是红色节点,Rr2型 或者 vR的右孩子是红色节点,Rr1(2)型 两个合起来就是vR的右孩子存在且是红色节点// 两种操作都是一样的if(vR && vR->rightChild && vR->rightChild->isRed){rotateRr1_2and2(pp);// OK1pp->isRed = false;break;}else if(vR && vR->leftChild && vR->leftChild->isRed){// 如果vR的左孩子是红色节点,Rr1(1)型rotateLR(pp);// OK1if(vR->leftChild->rightChild)vR->leftChild->rightChild->isRed = false;break;}else{// 如果v的左右孩子都是黑色节点,Rr0型,没找到合适的树来测试// 如果c节点是nullptr的话,树本来就是平衡的,不需要调整了// OK1rotateLL(pp);pp->isRed = false;// 更改原来的v节点为黑色节点if(pp->rightChild->leftChild)pp->rightChild->leftChild->isRed = true;break;}}}}}if(root && root->isRed == true)root->isRed = false;delete p;
}//判断是否为红黑树
template<class K, class E>
bool RedBlackTree<K, E>::ISRBTree()
{if (root == nullptr) //空树是红黑树{return true;}if (root->isRed == true){cout << "error: root is red" << endl;return false;}//找最左路径作为黑色结点数目的参考值RedBlackTreeNode<pair<K, E> > *cur = root;int BlackCount = 0;while (cur){if (cur->isRed == false)BlackCount++;cur = cur->leftChild;}int count = 0;return _ISRBTree(root, count, BlackCount);
}
//判断是否为红黑树的子函数
template<class K, class E>
bool RedBlackTree<K, E>::_ISRBTree(RedBlackTreeNode<pair<K, E> > * cur, int count, int BlackCount)
{if (cur == nullptr) //该路径已经走完了{if (count != BlackCount){cout << "error: black nodes nums not equal" << endl;return false;}return true;}if (cur->isRed == true && cur->parent->isRed == true){cout << "error: red and red nodes together" << endl;return false;}if (cur->isRed == false){count++;}return _ISRBTree(cur->leftChild, count, BlackCount) && _ISRBTree(cur->rightChild, count, BlackCount);
}// overload << for pair
template<class K, class E>
ostream &operator<<(ostream &out, const pair<K, E> &x) {out << x.first << ":" << x.second;return out;
}template<class K, class E>
ostream &operator<<(ostream &out, const RedBlackTreeNode<pair<K, E>> &x) {out << x.element.first << ":" << x.element.second << "  isRed = " << x.isRed;return out;
}#endif //_35RED_BLACK_TREE_REDBLACKTREE_H

RedBlackTree.cpp

/*
Project name :			_35Red_black_tree
Last modified Date:		2024年1月9日21点10分
Last Version:			V1.0
Descriptions:			红黑树测试函数
*/
#include "RedBlackTree.h"
#include<vector>void RedBlackTreeTest() {cout << "*************************RedBlackTreeTest() begin*******************************" << endl;RedBlackTree<int, char> y;pair<int, char> data;for(int i = 1; i < 1000001; i++){data = pair<int, char>(i, 'e');y.insert(data);}// 第二种删除策略for(int i = 250000; i > 0; i--){cout << " Delete " << i << endl;y.erase(i);cout << "y.ISRBTree() = " << y.ISRBTree() << endl;}for(int i = 500000; i < 750001; i++){cout << " Delete " << i << endl;y.erase(i);cout << "y.ISRBTree() = " << y.ISRBTree() << endl;}for(int i = 250001; i < 500000; i++){cout << " Delete " << i << endl;y.erase(i);cout << "y.ISRBTree() = " << y.ISRBTree() << endl;}for(int i = 1000000; i > 750000; i--){cout << " Delete " << i << endl;y.erase(i);cout << "y.ISRBTree() = " << y.ISRBTree() << endl;}cout << "***************************RedBlackTreeTest() end*******************************" << endl;
}

RedBlackTreeNode.h

/*
Project name :			_35Red_black_tree
Last modified Date:		2024年1月5日21点00分
Last Version:			V1.0
Descriptions:			红黑树树节点模板类
*/#ifndef _35RED_BLACK_TREE_REDBLACKTREENODE_H
#define _35RED_BLACK_TREE_REDBLACKTREENODE_Htemplate<class T>
struct RedBlackTreeNode {T element;// 存储键值对RedBlackTreeNode<T> *leftChild,   // 左子树*rightChild, // 右子树*parent;  // 父亲节点bool isRed; // true表示为红色,false表示为黑色RedBlackTreeNode() {leftChild = rightChild = parent = nullptr;isRed = true;}explicit RedBlackTreeNode(T &theElement) : element(theElement) {leftChild = rightChild = parent = nullptr;isRed = true;}explicit RedBlackTreeNode(T &theElement,RedBlackTreeNode *theLeftChild,RedBlackTreeNode *theRightChild,RedBlackTreeNode *theParent): element(theElement), leftChild(theLeftChild),rightChild(theRightChild), parent(theParent) {isRed = true;}explicit RedBlackTreeNode(T &theElement,RedBlackTreeNode *theLeftChild,RedBlackTreeNode *theRightChild,RedBlackTreeNode *theParent, bool theIsRed): element(theElement), leftChild(theLeftChild), rightChild(theRightChild), parent(theParent) {isRed = theIsRed;}
};#endif //_35RED_BLACK_TREE_REDBLACKTREENODE_H

dictionary.h

/*
Project name :			_35Red_black_tree
Last modified Date:		2024年1月6日18点17分
Last Version:			V1.0
Descriptions:			红黑树模板类
*/#ifndef _35RED_BLACK_TREE_REDBLACKTREE_H
#define _35RED_BLACK_TREE_REDBLACKTREE_H#include "RedBlackTreeNode.h"
#include "dictionary.h"
#include <stack>void RedBlackTreeTest();using namespace std;template<class K, class E>
class RedBlackTree : public dictionary<K, E> {
public:RedBlackTree() {root = nullptr;treeSize = 0;}[[nodiscard]] bool empty() const { return treeSize == 0; }[[nodiscard]] int size() const { return treeSize; }pair<K, E> *find(K theKey) const;void insert(pair<K, E> &thePair);void erase(K theKey);/*中序遍历二叉树,使用函数指针的目的是是的本函数可以实现多种目的*/void inOrder(void(*theVisit)(RedBlackTreeNode<pair<K, E>> *)) {visit = theVisit;/*是因为递归,所以才要这样的*/inOrder(root);/*这里调用的是静态成员函数inOrder()*/}/*中序遍历---输出endl*/void inOrderOutput() {inOrder(output);cout << endl;}/*前序遍历二叉树,使用函数指针的目的是是的本函数可以实现多种目的*/void preOrder(void(*theVisit)(RedBlackTreeNode<pair<K, E>> *)) {visit = theVisit;int num = 0;/*是因为递归,所以才要这样的*/preOrder(root, num);/*这里调用的是静态成员函数preOrder()*/cout << "num = " << num << endl;}/*中序遍历---输出endl*/void preOrderOutput() {preOrder(output);cout << endl;}bool ISRBTree();private:RedBlackTreeNode<pair<K, E>> *root;// 指向根的指针int treeSize;// 树的结点个数static void (*visit)(RedBlackTreeNode<pair<K, E>> *);//是一个函数指针,返回值为void 函数参数为binaryTreeNode<pair<K, E>>*static void output(RedBlackTreeNode<pair<K, E>> *t) { cout << *t << endl; }static void inOrder(RedBlackTreeNode<pair<K, E>> *t);static void preOrder(RedBlackTreeNode<pair<K, E>> *t, int& num);void rotateLL(RedBlackTreeNode<pair<K, E>> *&x);void rotateLR(RedBlackTreeNode<pair<K, E>> *&x);void rotateRR(RedBlackTreeNode<pair<K, E>> *&x);void rotateRL(RedBlackTreeNode<pair<K, E>> *&x);void rotateRr1_2and2(RedBlackTreeNode<pair<K, E>> *&pp);void rotateLr1_2and2(RedBlackTreeNode<pair<K, E>> *&pp);bool _ISRBTree(RedBlackTreeNode<pair<K, E> > * root, int count, int BlackCount);
};/*私有静态成员初始化*/
/*这里是静态函数指针成员的初始化,不初始化会引发LINK错误*/
template<class K, class E>
void (*RedBlackTree<K, E>::visit)(RedBlackTreeNode<pair<K, E>> *) = 0;      // visit function/*中序遍历 递归*/
template<class K, class E>
void RedBlackTree<K, E>::inOrder(RedBlackTreeNode<pair<K, E>> *t) {if (t != nullptr) {inOrder(t->leftChild);/*中序遍历左子树*/visit(t);/*访问树根*/inOrder(t->rightChild);/*中序遍历右子树*/}
}/*前序遍历 递归*/
template<class K, class E>
void RedBlackTree<K, E>::preOrder(RedBlackTreeNode<pair<K, E>> *t, int& num) {if (t != nullptr) {visit(t);/*访问树根*/num++;preOrder(t->leftChild, num);/*中序遍历左子树*/preOrder(t->rightChild, num);/*中序遍历右子树*/}
}/*  查找元素*  输入:theKey表示需要查找元素的键值*  输出:键值为theKey的节点的pair地址*  时间复杂度:O(logn),n表示节点个数*/
template<class K, class E>
pair<K, E> *RedBlackTree<K, E>::find(K theKey) const {// 返回值是匹配数对的指针// 如果没有匹配的数对,返回值为nullptr// p从根节点开始搜索,寻找关键字等于theKey的一个元素RedBlackTreeNode<pair<K, E> > *p = root;while (p != nullptr)// 检查元素 p->elementif (theKey < p->element.first)p = p->leftChild;else if (theKey > p->element.first)p = p->rightChild;else // 找到匹配的元素return &p->element;// 没找到匹配的元素return nullptr;
}/**  LL旋转*  输入:x是第一个L的父亲节点,插入元素和删除元素都会用到*  输出:void*  时间复杂度:O(1)*  注意事项:执行本函数前后x指向的元素会改变为新的该子树的根节点*/
template<class K, class E>
void RedBlackTree<K, E>::rotateLL(RedBlackTreeNode<pair<K, E>> *&x) {// 记录祖父节点的父亲节点RedBlackTreeNode<pair<K, E>> *Parent = x->parent;RedBlackTreeNode<pair<K, E>> *b = x->leftChild;x->leftChild = b->rightChild;if(b->rightChild)b->rightChild->parent = x;b->rightChild = x;// x的父亲节点变为bx->parent = b;// b的父亲节点变为原来x的父亲节点b->parent = Parent;// 这里就是原来祖父节点的父亲现在需要作为b的父亲,前提是祖父节点的父亲存在if (Parent != nullptr) {if (x == Parent->leftChild)Parent->leftChild = b;elseParent->rightChild = b;} elseroot = b;// 祖父节点如果没有父亲的话就是根节点x = b;// b节点将替换x节点
}/**  RR旋转*  输入:x表示第一个R的父亲节点,在插入和删除时都会用到*  输出:void*  时间复杂度:O(1)*  注意事项:执行本函数前后x指向的元素会改变为新的该子树的根节点*/
template<class K, class E>
void RedBlackTree<K, E>::rotateRR(RedBlackTreeNode<pair<K, E>> *&x) {// 记录祖父节点的父亲节点RedBlackTreeNode<pair<K, E>> *Parent = x->parent;RedBlackTreeNode<pair<K, E>> *b = x->rightChild;x->rightChild = b->leftChild;if(b->leftChild)b->leftChild->parent = x;b->leftChild = x;x->parent = b;// x的父亲节点为b// b的父亲节点为祖父节点的父亲节点b->parent = Parent;// 这里就是原来祖父节点的父亲现在需要作为b的父亲,前提是祖父节点的父亲存在if (Parent != nullptr) {if (x == Parent->leftChild)Parent->leftChild = b;elseParent->rightChild = b;} elseroot = b;// 祖父节点如果没有父亲的话就是根节点x = b;
}/**  LR旋转*  输入:x表示L的父亲节点,插入元素和删除元素时都会用到*  输出:void*  时间复杂度:O(1)*  注意事项:执行本函数前后x指向的元素会改变为新的该子树的根节点*/
template<class K, class E>
void RedBlackTree<K, E>::rotateLR(RedBlackTreeNode<pair<K, E>> *&x) {rotateRR(x->leftChild);rotateLL(x);
}/**  RL旋转*  输入:x表示R的父亲节点,插入元素和删除元素都会用到*  输出:void*  时间复杂度:O(1)*  注意事项:执行本函数前后x指向的元素会改变为新的该子树的根节点*/
template<class K, class E>
void RedBlackTree<K, E>::rotateRL(RedBlackTreeNode<pair<K, E>> *&x) {rotateLL(x->rightChild);rotateRR(x);
}/** 删除节点时:对于Rr1(2)型和Rr2型的旋转使用* 输入:替换被删除节点的y节点的父亲节点* 输出:void* 时间复杂度:O(1)*/
template<class K, class E>
void RedBlackTree<K, E>::rotateRr1_2and2(RedBlackTreeNode<pair<K, E>> *&pp) {RedBlackTreeNode<pair<K, E>> *Parent = pp->parent;RedBlackTreeNode<pair<K, E>> *w = pp->leftChild->rightChild;RedBlackTreeNode<pair<K, E>> *x = w->rightChild;if(x != nullptr){w->rightChild = x->leftChild;if(x->leftChild)x->leftChild->parent = w;// 父亲节点要及时更改}else{cout << "rotateRr1_2and2 error!" << endl;}x->leftChild = pp->leftChild;pp->leftChild->parent = x;pp->leftChild = x->rightChild;if(x->rightChild)x->rightChild->parent = pp;x->rightChild = pp;pp->parent = x;if(Parent->leftChild == pp)Parent->leftChild = x;elseParent->rightChild = x;x->parent = Parent;pp = x;
}/** 删除节点时,对于Lr1(2)型和Lr2型的旋转使用* 输入:替换被删除节点的y节点的父亲节点* 输出:void* 时间复杂度:O(1)*/
template<class K, class E>
void RedBlackTree<K, E>::rotateLr1_2and2(RedBlackTreeNode<pair<K, E>> *&pp) {RedBlackTreeNode<pair<K, E>> *Parent = pp->parent;RedBlackTreeNode<pair<K, E>> *w = pp->rightChild->leftChild;RedBlackTreeNode<pair<K, E>> *x = w->leftChild;if(x != nullptr){w->leftChild = x->rightChild;if(x->rightChild)x->rightChild->parent = w;// 父亲节点要及时更改}else{cout << "rotateLr1_2and2 error!" << endl;}x->rightChild = pp->rightChild;pp->rightChild->parent = x;pp->rightChild = x->leftChild;if(x->leftChild)x->leftChild->parent = pp;x->leftChild = pp;pp->parent = x;if(Parent->leftChild == pp)Parent->leftChild = x;elseParent->rightChild = x;x->parent = Parent;pp = x;
}/**  插入元素*  输入:const pair<K, E> thePair表示需要插入的键值对*  输出:void*  时间复杂度:O(logn),表示节点个数*/
template<class K, class E>
void RedBlackTree<K, E>::insert(pair<K, E> &thePair) {// 当红黑树为空时,插入节点直接作为根节点,且其颜色为黑色if (root == nullptr) {root = new RedBlackTreeNode<pair<K, E> >(thePair, nullptr, nullptr, nullptr, false);treeSize++;return;}// 如果树非空. 如果该键值存在则覆盖元素// 寻找插入位置RedBlackTreeNode<pair<K, E> > *p = root,*pp = nullptr;while (p != nullptr) {// 检查元素 p->elementpp = p;// 如果当前键值小于p的键值,则移到p的左孩子if (thePair.first < p->element.first)p = p->leftChild;else// 如果当前键值大于p的键值,则移到p的右孩子if (thePair.first > p->element.first)p = p->rightChild;else {// 如果键值相等,覆盖旧的值p->element.second = thePair.second;return;}}// 为thePair建立一个节点,然后与pp链接,此时pp是叶子节,默认插入为红色节点auto *newNode = new RedBlackTreeNode<pair<K, E> >(thePair);// 如果thePair的键值小于pp的键值,则将thePair作为pp的左孩子,反之将其作为右孩子if (thePair.first < pp->element.first) {pp->leftChild = newNode;newNode->parent = pp;} else {pp->rightChild = newNode;newNode->parent = pp;}treeSize++;p = newNode;// 如果父亲节点存在且父亲节点是红色的,则需要对红黑树进行调整while (pp && pp->isRed == true) {// parent是红色,则其父节点一定存在RedBlackTreeNode<pair<K, E> > *grandfather = pp->parent;if (pp == grandfather->leftChild) {// parent是grandfather的左孩子// LYr(Y可以是L或R)的情况RedBlackTreeNode<pair<K, E> > *uncle = grandfather->rightChild;// 找到grandfather的右孩子,可以称为叔叔if (uncle && uncle->isRed == true) {// 如果叔叔存在且为红色// 将父亲节点和其叔叔节点的颜色更改为黑色pp->isRed = uncle->isRed = false;// 只有在祖父节点不为根节点时,将祖父节点的颜色更改为红色if (grandfather != root)grandfather->isRed = true;else // 如果祖父节点已经是根节点了,那么就直接终止循环,因为不可能再向上处理了break;// 继续往上处理p = grandfather;pp = p->parent;} else {// uncle不存在或者uncle存在且为黑色// 如果当前节点是其父亲节点的左孩子// LLb的情况:左孩子的左孩子if (p == pp->leftChild) {rotateLL(grandfather);// LL单旋// 调整颜色// 现在的grandfather指向的是原来的父亲节点,grandfather的右孩子指向的是原祖父节点grandfather->isRed = false;grandfather->rightChild->isRed = true;} else {// LRb的情况rotateLR(grandfather);// 调整颜色// 现在的grandfather指向的是原来的新节点u,u的左孩子是原来的父亲节点pu,u的右孩子是原来的祖父节点gugrandfather->isRed = false;grandfather->rightChild->isRed = true;}break;// 无需继续向上进行处理}} else {// parent是grandfather的右孩子// RYr(Y可以是L或R)的情况RedBlackTreeNode<pair<K, E> > *uncle = grandfather->leftChild;// 找到grandfather的左孩子,可以称为叔叔if (uncle && uncle->isRed == true) {// 如果叔叔存在且为红色// 将父亲节点和其叔叔节点的颜色更改为黑色pp->isRed = uncle->isRed = false;// 只有在祖父节点不为根节点时,将祖父节点的颜色更改为红色if (grandfather != root)grandfather->isRed = true;else // 如果祖父节点已经是根节点了,那么就直接终止循环,因为不可能再向上处理了break;// 继续往上处理p = grandfather;pp = p->parent;} else {// uncle不存在或者uncle存在且为黑色// 如果当前节点是其父亲节点的右孩子// RRb的情况:右孩子的右孩子if (p == pp->rightChild) {rotateRR(grandfather);// RR单旋// 调整颜色// 现在的grandfather指向的是原来的父亲节点,grandfather的左孩子指向的是原祖父节点grandfather->isRed = false;grandfather->leftChild->isRed = true;} else {// LRb的情况rotateRL(grandfather);// 调整颜色// 现在的grandfather指向的是原来的新节点u,u的右孩子是原来的父亲节点pu,u的左孩子是原来的祖父节点gugrandfather->isRed = false;grandfather->leftChild->isRed = true;}break;// 无需继续向上进行处理}}}
}/**  删除元素*  输入:const K theKey表示需要删除元素的键值*  输出:void*  时间复杂度:O(logn),n表示节点个数*/
template<class K, class E>
void RedBlackTree<K, E>::erase(K theKey) {// 删除关键字等于theKey的数对// 查找关键字为theKey的节点RedBlackTreeNode<pair<K, E> > *p = root,*pp = nullptr;while (p != nullptr && p->element.first != theKey){pp = p;if (theKey < p->element.first)p = p->leftChild;elsep = p->rightChild;}if (p == nullptr){cout << theKey << " not exist!" << endl;return; // 不存在与关键字theKey匹配的数对}// 重新组织树结构// 当p有两个孩子时的处理if (p->leftChild != nullptr && p->rightChild != nullptr){// 两个孩子// 在P的左子树中寻找最大元素RedBlackTreeNode<pair<K, E> > *s = p->leftChild,*ps = p;  // s的父节点while (s->rightChild != nullptr){// 移动到更大的pairps = s;s = s->rightChild;// 右孩子比较大}// 将最大元素s移到p// p->element = s->element 的键值是 const// 当最大值就是p的左孩子时,new的元素不能直接指向p的左孩子,而要指向p的左孩子的左孩子(此时p的左孩子没有右孩子),因为到时候s会被delete掉,这个问题在后面的p至多有一个孩子那里解决的RedBlackTreeNode<pair<K, E> >* q = nullptr;// 值用s的值替换,颜色和其他指针的指向都是p的q = new RedBlackTreeNode<pair<K, E> >(s->element, p->leftChild, p->rightChild, p->parent, p->isRed);if(p->leftChild)p->leftChild->parent = q;if(p->rightChild)p->rightChild->parent = q;// pp是p的父节点// 如果p没有父节点if (pp == nullptr)root = q;else if (p == pp->leftChild)// 如果p是pp的左孩子pp->leftChild = q;else// 如果p是pp的右孩子pp->rightChild = q;// 如果s的父节点就是p,说明p节点的左子树只有左子树没有右子树// 那么删除p后pp就是其父节点if (ps == p) pp = q;else pp = ps;// 反之ps是其父节点delete p;p = s;}// p至多只有一个孩子// 把孩子的指针存放到cRedBlackTreeNode<pair<K, E> > *c;if (p->leftChild != nullptr)c = p->leftChild;elsec = p->rightChild;// 删除pbool isLeft = false;// 记录p节点是左孩子还是右孩子if (p == root)root = c;else{// p是pp的左孩子还是右孩子if (p == pp->leftChild){pp->leftChild = c;// 更新一下c节点的父亲节点if(c != nullptr)c->parent = pp;isLeft = true;}else{pp->rightChild = c;// 更新一下c节点的父亲节点if(c != nullptr)c->parent = pp;}}treeSize--;// 之前就找到了y节点的父亲节点pp// 如果被删除节点是红色的,则红黑树还是平衡的;如果被删除节点是黑色的,则红黑树需要调整if(p->isRed == false){// 父亲节点不为空,说明还需要继续调整while(pp){// 如果c节点不为空,则需要判断c节点是左孩子还是右孩子// 这个主要是用于第二轮调整平衡,也就是c节点此时不再是p的孩子节点if(c){if(c == pp->leftChild)isLeft = true;elseisLeft = false;}if(isLeft){// 找到被删除节点的兄弟RedBlackTreeNode<pair<K, E> > *v = pp->rightChild;// 如果兄弟节点是黑色节点 Lb型// 如果c和v都为空的话,那么不用调整平衡if(!c && !v)break;if(!v->isRed){// 如果v的左右孩子都是红色节点,Lb2型;或者v的左孩子是红色节点,Lb1(2)型if(v->leftChild && v->leftChild->isRed){rotateRL(pp);// OK1pp->isRed = pp->leftChild->isRed;pp->leftChild->isRed = false;break;}else if(v->rightChild && v->rightChild->isRed){// 如果v的左孩子是红色节点,Lb1(1)型rotateRR(pp);// OK1pp->isRed = pp->leftChild->isRed;// 这个节点的颜色不变pp->leftChild->isRed = false;// 原来的祖先节点的颜色变为黑色节点v->rightChild->isRed = false;// 将v的左孩子更改为黑色节点break;// 不需要继续调整了}else{// 如果v的左右孩子都是黑色节点,Lb0型// pp是红色节点,改变pp为黑色节点,改变v为红色节点if(pp->isRed){pp->isRed = false;// OK1v->isRed = true;break;// 终止循环,不需要再继续调整了}else{// pp是黑色节点,将v节点改变为红色节点,然后将pp节点作为新的y节点,也就是程序中的p节点v->isRed = true;//OK1// 继续调整平衡c = pp;pp = c->parent;}}}else{// 如果兄弟节点是红色节点 Lr型,则兄弟节点一定有左右孩子// 找到被删除节点的兄弟的左孩子RedBlackTreeNode<pair<K, E> > *vL = v->leftChild;// 如果vL的左右孩子都是红色节点,Lr2型 或者 如果v的左孩子是红色节点,Lr1(2)型if(vL && vL->leftChild && vL->leftChild->isRed){rotateLr1_2and2(pp);// OK1pp->isRed = false;break;}else if(vL && vL->rightChild && vL->rightChild->isRed){// 如果v的右孩子是红色节点,Lr1(1)型rotateRL(pp);// OK1if(vL->rightChild->leftChild)vL->rightChild->leftChild->isRed = false;break;}else{// 如果v的左右孩子都是黑色节点,Lr0型rotateRR(pp);// OK1pp->isRed = false;// 更改原来的v节点为黑色节点if(pp->leftChild->rightChild)pp->leftChild->rightChild->isRed = true;break;}}}else{// 否则是R型// 找到被删除节点的兄弟RedBlackTreeNode<pair<K, E> > *v = pp->leftChild;// 如果c和v都为空的话,那么树本身就是平衡的if((!c && !v))break;// 如果兄弟节点是黑色节点 Rb型if(!v->isRed){// 如果v的左右孩子都是红色节点,Rb2型;或者v的右孩子是红色节点,Rb1(2)型if(v->rightChild && v->rightChild->isRed){rotateLR(pp);// OK1pp->isRed = pp->rightChild->isRed;pp->rightChild->isRed = false;break;}else if(v->leftChild && v->leftChild->isRed){// 如果v的左孩子是红色节点,Rb1(1)型rotateLL(pp);// OK1pp->isRed = pp->rightChild->isRed;// 这个节点的颜色不变pp->rightChild->isRed = false;// 原来的祖先节点的颜色变为黑色节点v->leftChild->isRed = false;// 将v的左孩子更改为黑色节点break;// 不需要继续调整了}else{// 如果v的左右孩子都是黑色节点,Rb0型// pp是红色节点,改变pp为黑色节点,改变v为红色节点if(pp->isRed){pp->isRed = false;// OK1v->isRed = true;break;// 终止循环,不需要再继续调整了}else{// pp是黑色节点,将v节点改变为红色节点,然后将pp节点作为新的y节点,也就是程序中的p节点// 这个没有找到合适的节点去测试v->isRed = true;//OK1c = pp;pp = c->parent;}}}else{// 如果兄弟节点v是红色节点 Rr型,则兄弟节点v一定有左右孩子// 找到被删除节点的兄弟的右孩子RedBlackTreeNode<pair<K, E> > *vR = v->rightChild;// 如果vR的左右孩子都是红色节点,Rr2型 或者 vR的右孩子是红色节点,Rr1(2)型 两个合起来就是vR的右孩子存在且是红色节点// 两种操作都是一样的if(vR && vR->rightChild && vR->rightChild->isRed){rotateRr1_2and2(pp);// OK1pp->isRed = false;break;}else if(vR && vR->leftChild && vR->leftChild->isRed){// 如果vR的左孩子是红色节点,Rr1(1)型rotateLR(pp);// OK1if(vR->leftChild->rightChild)vR->leftChild->rightChild->isRed = false;break;}else{// 如果v的左右孩子都是黑色节点,Rr0型,没找到合适的树来测试// 如果c节点是nullptr的话,树本来就是平衡的,不需要调整了// OK1rotateLL(pp);pp->isRed = false;// 更改原来的v节点为黑色节点if(pp->rightChild->leftChild)pp->rightChild->leftChild->isRed = true;break;}}}}}if(root && root->isRed == true)root->isRed = false;delete p;
}//判断是否为红黑树
template<class K, class E>
bool RedBlackTree<K, E>::ISRBTree()
{if (root == nullptr) //空树是红黑树{return true;}if (root->isRed == true){cout << "error: root is red" << endl;return false;}//找最左路径作为黑色结点数目的参考值RedBlackTreeNode<pair<K, E> > *cur = root;int BlackCount = 0;while (cur){if (cur->isRed == false)BlackCount++;cur = cur->leftChild;}int count = 0;return _ISRBTree(root, count, BlackCount);
}
//判断是否为红黑树的子函数
template<class K, class E>
bool RedBlackTree<K, E>::_ISRBTree(RedBlackTreeNode<pair<K, E> > * cur, int count, int BlackCount)
{if (cur == nullptr) //该路径已经走完了{if (count != BlackCount){cout << "error: black nodes nums not equal" << endl;return false;}return true;}if (cur->isRed == true && cur->parent->isRed == true){cout << "error: red and red nodes together" << endl;return false;}if (cur->isRed == false){count++;}return _ISRBTree(cur->leftChild, count, BlackCount) && _ISRBTree(cur->rightChild, count, BlackCount);
}// overload << for pair
template<class K, class E>
ostream &operator<<(ostream &out, const pair<K, E> &x) {out << x.first << ":" << x.second;return out;
}template<class K, class E>
ostream &operator<<(ostream &out, const RedBlackTreeNode<pair<K, E>> &x) {out << x.element.first << ":" << x.element.second << "  isRed = " << x.isRed;return out;
}#endif //_35RED_BLACK_TREE_REDBLACKTREE_H

完整可编译运行代码见仓库:GitHub - Jasmine-up/Data-Structures-Algorithms-and-Applications/_35Red black tree。
如有问题请在评论区指出。另外,Github仓库会根据我的学习情况持续更新,欢迎大家点star,谢谢。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/630328.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

指纹浏览器为什么要搭配代理IP?如何选择?

跨境电商无论是店群模式还是社媒矩阵运营&#xff0c;都必须涉及管理多个社媒/电商帐户的动作&#xff0c;但这很容易引发网站怀疑并最终被批量封号。使用指纹浏览器浏览器的主要目的是通过创建新的浏览器指纹来隐藏用户的真实浏览器指纹。 但浏览器指纹并不是网站关注的唯一…

ELK之Filebeat安装配置及日志抓取

一、Filebeat是什么 轻量型日志采集器 无论您是从安全设备、云、容器、主机还是 OT 进行数据收集,Filebeat 都将为您提供一种轻量型方法,用于转发和汇总日志与文件,让简单的事情不再繁杂。 Filebeat 随附可观测性和安全数据源模块,这些模块简化了常见格式的日志的收集、解…

Qt/QML编程之路:Grid、GridLayout、GridView、Repeater(33)

GRID网格用处非常大,不仅在excel中,在GUI中,也是非常重要的一种控件。 Grid 网格是一种以网格形式定位其子项的类型。网格创建一个足够大的单元格网格,以容纳其所有子项,并将这些项从左到右、从上到下放置在单元格中。每个项目都位于其单元格的左上角,位置为(0,0)。…

【Docker】Dockerfile构建最小镜像

&#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Docker的相关操作吧 目录 &#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 前言 一.Dockerfile是什么 二.Dock…

【算法】队列+bfs算法 解决树的相关算法题(C++)

文章目录 1. 前言2. 算法题429.N叉树的层序遍历103.二叉树的锯齿形层序遍历662.二叉树最大宽度515.在每个树行中找最大值 1. 前言 队列 与 宽度优先算法&#xff08;BFS&#xff09;是解决很多算法问题的常见工具。 BFS通过逐层遍历图或树的节点来寻找解决问题的最短路径或最…

【idea】idea插件编写教程,博主原创idea插件已上架idea插件市场 欢迎下载

前言&#xff1a;经常使用Objects.equals(a,b)方法的同学 应该或多或少都会因为粗心而传错参&#xff0c; 例如日常开发中 我们使用Objects.equals去比较 status(入参)&#xff0c;statusEnum(枚举), 很容易忘记statusEnum.getCode() 或 statusEnum.getVaule() &#xff0c;再比…

C++ 多条件比较的几种实现方式

文章目录 1 sort()使用自定义比较器1.1 在类内部定义比较器 -- 声明为静态成员函数1.2 在函数内部定义比较器 -- lamda表达式1.3 全局函数比较器 2 重载运算符<2.1 在结构体中重载运算符<2.2 在类中重载运算符< 3 重写仿函数bool operator()4 使用pair排序5 priority_…

IPO:动力电池行业变天,不生产电芯的幂源科技为何也要卷?

幂源科技的真正角色&#xff0c;是月老&#xff1f; 最近&#xff0c;动力电池正处于市场重构阶段。一边是供给端动力电池产能过剩&#xff0c;一边是需求端新能源车企加码电池自研。供需不匹配孕育出了新机遇&#xff0c;产业链的连接者与赋能者成为市场所需。而幂源科技控股…

学生用台灯哪个品牌比较好?口碑最好的护眼台灯推荐

台灯是现在很多朋友都在使用的照明灯具&#xff0c;对于晚上工作学习、看书休闲都有很大的裨益&#xff0c;但是选择台灯也注重护眼效果&#xff0c;在此基础上才是讨论性价比的问题&#xff0c;如果光线让人眼不舒适&#xff0c;那么多便宜的台灯都不划算。 ● 怎样的护眼台灯…

基于FPGA的万兆以太网学习(1)

万兆(10G) 以太网测速视频:FPGA 实现UDP万兆以太网的速度测试 1 代码结构 2 硬件需求 SFP+屏蔽笼可以插入千兆或万兆光模块。SFP+信号定义与 SFP 一致。 3 Xilinx IP 10 Gigabit Ethernet Subsystem IP说明 文章链接: Xilinx IP 10 Gigabit Ethernet Subsystem IP 4 E…

Vue3响应式系统(二)

Vue3响应式系统(一)https://blog.csdn.net/qq_55806761/article/details/135587077 六、嵌套的effect与effect栈。 什么场景会用到effect嵌套呢&#xff1f;听我娓娓道来。 就用Vue.js来说吧&#xff0c;Vue.js的渲染函数就是在effect中执行的&#xff1a; /*Foo组件*/ const…

循环神经网络的变体模型-LSTM、GRU

一.LSTM&#xff08;长短时记忆网络&#xff09; 1.1基本介绍 长短时记忆网络&#xff08;Long Short-Term Memory&#xff0c;LSTM&#xff09;是一种深度学习模型&#xff0c;属于循环神经网络&#xff08;Recurrent Neural Network&#xff0c;RNN&#xff09;的一种变体。…

Android NDK Crash信息收集捕获和日志异常定位分析(addr2line)

Android NDK 闪退日志收集与分析 我们在开发过程中,Android JNI层Crash问题或者我们引用的第三方.so库文件报错,都是一个比较头疼的问题。相对Java层来说,由于c/c++造成的crash没有输出如同Java的Exception Strace堆栈信息,所以定位问题也是个比较艰难的事情。 Google Br…

HCIA的路由协议

动态路由协议/静态路由协议 静态路由协议和动态路由协议的区别&#xff1a; 静态路由协议的缺点&#xff1a; 配置繁琐 针对拓扑的变化不能够自动收敛 只适用于小型网络 静态路由协议优点&#xff1a; 占用资源少 安全 稳定 动态路由协议的优点&#xff1a; 配置简单 针对拓…

前端项目配置 Dockerfile 打包后镜像部署无法访问

Dockerfile 配置如下&#xff1a; FROM node:lts-alpineWORKDIR /app COPY . . RUN npm install RUN npm run buildEXPOSE 3001CMD ["npm", "run", "preview"]构建镜像 docker build -t vite-clarity-project .启动镜像容器 docker run -p 30…

进程(一) 进程概念

文章目录 什么是进程呢&#xff1f; 描述进程-PCBtask_struct-PCB的一种task_struct内容分类 查看进程通过系统目录查看通过ps命令查看通过系统调用获取进程的PID和PPID通过系统调用创建进程- fork&#xff08;&#xff09;函数 fork()函数fork函数做了什么&#xff1f;fork之后…

Vue加载序列帧动图

解读方法 使用<img :src"currentFrame" alt"加载中" /> 加载图片动态更改src的值使用 requestAnimationFrame 定时更新在需要的页面调用封装的组件 <LoadToast v-if"showLoading" /> 封装组件 <template><div class"…

CTF CRYPTO 密码学-1

题目名称&#xff1a;enc 题目描述&#xff1a; 压缩包中含两个文件&#xff1a;一个秘钥d.dec&#xff0c;一个密文flag.enc 解题过程&#xff1a; Step1&#xff1a;这题是一个解密他题目&#xff0c;尝试openssl去ras解密 工具简介 在Kali Linux系统中&#xff0c;openss…

React16源码: React中的异步调度scheduler模块的源码实现

React Scheduler 1 ) 概述 react当中的异步调度&#xff0c;称为 React Scheduler发布成单独的一个 npm 包就叫做 scheduler这个包它做了什么&#xff1f; A. 首先它维护时间片B. 然后模拟 requestIdleCallback 这个API 因为现在浏览器的支持不是特别的多所以在浏览当中只是去…

【计算机图形学】习题课:Viewing

【计算机图形学】Viewing 部分问题与解答 CS100433 Computer Graphics Assignment 21 Proof the composed transformations defined in global coordinate frame is equivalent to the composed transformations defined in local coordinate frame but in different composing…