【C++】 使用红黑树模拟实现STL中的map与set

news2024/11/16 0:31:58

文章目录

  • 前言
  • 1. 对之前实现的红黑树进行一些补充和完善
    • 1.1 析构
    • 1.2 查找
  • 2. STL源码中map和set的实现
  • 3. 改造红黑树+封装map和set
    • 3.1 红黑树结构修改
    • 3.2 map、set的结构定义
    • 3.3 insert的封装
    • 3.4 insert测试
    • 3.5 发现问题并解决
    • 3.6 红黑树迭代器实现
    • 3.7 封装set和map的迭代器并测试
    • 3.8 map的[]重载
    • 3.9 元素可以修改的问题解决
  • 4. 代码展示
    • 4.1 RBTree.h
    • 4.2 Map.h
    • 4.3 Set.h
    • 4.4 Test.cpp

前言

前面的文章我们学习了红黑树,也提到了C++STL中的map和set的底层其实就是用的红黑树来实现的(而map和set的使用我们前面也学过了)。
既然红黑树我们也学习过了,那这篇文章我们就用红黑树来简单实现一下STL中的map和set,重点是学习它的框架。

1. 对之前实现的红黑树进行一些补充和完善

上一篇文章我们实现了红黑树,但是我们实现的那个类并不是特别完善,所以,为了后面map和set的模拟实现,我们先对之前写的那个红黑树的类做一些补充和完善。

1.1 析构

构造函数的话其实不写也没事,我们给了缺省值,但是析构的话有必要写一下,因为我们的红黑树是涉及资源管理的。

我们来实现一下:

二叉树的销毁我们之前也有讲过,走一个后序遍历销毁就行了
在这里插入图片描述
代码很简单,我就不解释了

1.2 查找

那红黑树的查找呢我们上一篇文章有提到,但是当时没写,因为就跟搜索二叉树的查找一样嘛。

那这里由于后面我们要用红黑树模拟实现map和set(它们是有find这个接口的)的缘故,所以我们也补充一下:

直接上代码
在这里插入图片描述

2. STL源码中map和set的实现

那在正式实现之前,我们先一起来看一下STL(SGI版本)中map和set的源码,大致了解一下库里面是怎么实现的。

我们先来看一下map的源码:

由于源码里面内容比较多,我们这里不需要全部都看,所以我这里有选择性地提取一些内容给大家看
先来看这些
在这里插入图片描述
我们看到它的底层这个成员变量其实就是一棵红黑树,当然他这里面红黑树的类在另一个头文件里面实现,后面也会带大家看它里面的一些内容
然后我们继续:
我们之前说过map其实就对应搜索树的KV模型,那其实这里的key_typevalue_type就对应的是Key和Value的的类型,我们看到这里面的key_type就是key,但是value_type却是一个pair。
可以按我们之前的理解,map里面存的就是一个pair(键值对)嘛,pair的first对应key,second对应value。
但是它里面为什么value的类型就是一个pair呢?
那先不急,我们后面会解释。

然后我们再来看一下set:

在这里插入图片描述
首先set的底层也是红黑树,这没什么说到,我们都知道,但是我们会发现map和set底层用的是同一个红黑树的类模板。
但是它们不是一个对应KV模型,一个对应K模型嘛,那我们想的可能是应该实现两个红黑树,一棵存Key的单个值,另一棵存KV的键值对,然后用这两棵红黑树分别封装map和set。

那它这里是如何做到map和set底层都用同一个红黑树的类模板呢?

观察set的结构我们会发现:

在这里插入图片描述
set的里面虽然存的是单个值,可它除了有key_type之外,也有value_type,但是set里面的key_type和value_type都是Key的typedef,所以相当于传来两个key。
当然从表面上来看他之所以这样做的原因是他和map用的是同一个红黑树的类模板,而这个类模板是有两个参数的,所以它必须传两个。

那红黑树为什么要这样设计呢?所以我们再来观察一下红黑树的实现:

在这里插入图片描述
这里这个node_count其实就是记录了结点的数量,这个map和set需要获取size的时候就很方便,不需要再去遍历计数了。
然后下面这个link_type这个类型其实就是结点的指针,这个header代表啥我们可以先不管。
然后我们看到红黑树这里的模板参数是固定的,前两个模板参数是key和value,而红黑树结点里面放的其实就是value
在这里插入图片描述

所以它们这里这两个模板参数的传递是这样的:

在这里插入图片描述
这样如果是set,那红黑树的结点里面存的就是key,如果是map,那红黑树的结点里面存的就是pair的键值对。
所以这里第二个模板参数value接收的什么,就决定了红黑树结点里面存的是什么类型的数据。

那问题来了,第一个模板参数的作用是啥?

在这里插入图片描述
大家想一下map和set都有find,erase这些接口,那它们有什么不同?
🆗,对于set来说,它的查找是不是就是按结点里面存的那个key去查找的啊,返回的是对应元素的迭代器。
而map呢?
它里面存的是KV的键值对,但是它查找的时候是不是只拿K去查找啊
,因为key是唯一的,而value其实是可以重复的。
而map的查找返回的是整个pair的迭代器(其实还是结点里面元素的迭代器,map里面存的就是pair嘛。)

所以,我想大家现在应该就明白第一个模板参数Key是干嘛的了:

因为map里面存的是KV的键值对,但是查找这些操作的时候是以K为目标去查找的,所以红黑树里面要多搞出来一个参数Key来单独获取这个Key。
那其实对于set来说是不需要的(因为set里面存的就是单独一个key,查找也是用的key,上面我们也看了,它传的前两个模板就是一样的,都是key),但是因为你set要和map共用一个红黑树模板,所以你必须迎合这个红黑树的结构,也去多传一个。
当然其实map也可以不用第一个参数,查找的时候用pair.first就行了,但是set里面只存了一个key,它可找不来什么first的东西,所以红黑树才要增加一个模板参数,这样红黑树里面的find,erase这些函数就可以以一种统一的实现(即都用第一个模板参数接收的key去查找),这样map和set才可以共同复用同一个红黑树的模板类。

当然它们只是共用了同一个类模板而已,最后实例化出来的还是不同的类对象,但是这不就正是模板出现的主要意义嘛,实现代码的复用,对我们程序员来说还是方便了很多的。

那源码呢我们就先看到这里,接下来我们就来自己尝试模拟实现一下,其实就是去对红黑树进行一个封装嘛。
后面有需要我们再来看相应的源码。

3. 改造红黑树+封装map和set

3.1 红黑树结构修改

先要封装的话首先我们得对我们之前自己实现得红黑树做一些改造。

首先结点的结构我们可以不用改,虽然我们的跟库里面源码不太一样:

在这里插入图片描述
在这里插入图片描述
我们这里就还用我们直接写到就可以,只是实现方式不同,这里没什么影响。

那红黑树的结构我们就需要修改一下了:

因为我们当时是按照K模型实现的,只有一个模板参数在这里插入图片描述
所以要加一个,至于这里为什么需要两个上面已经解释过了
在这里插入图片描述
这里我们就用KT,大家知道代表什么就行了,就对应上面源码中红黑树的前两个模板参数嘛。
当然源码里面红黑树不止这两个模板参数,但我们不用管,有的我们不需要加,有的有需要的话我们后面再加。

3.2 map、set的结构定义

那然后我们来创建一个头文件,先把map的结构简单定义一下

在这里插入图片描述
因为map里面pair的键key不能修改,所以我们加一个const,库里面也是这样搞的。

然后写一下set的:

在这里插入图片描述

3.3 insert的封装

先来看map:

在这里插入图片描述
其实还是复用红黑树的Insert,当然之前我们学过map和set的使用,它们insert的返回值其实是一个pair嘛(当然只是插入一个元素的那个版本),大家可以去看一下文档或复习一些之前文章)
那我们这里先这样写,后面需要的时候再结合具体场景修改。

然后set:

也是一样
在这里插入图片描述

3.4 insert测试

我们分别对map和set插入一些数据试一试:

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

然后呢:

在这里插入图片描述
我们运行程序看起来是正常的(当然我们并没有打印出来看),但是这里面其实是存在问题的。

3.5 发现问题并解决

什么问题呢?

🆗,我们前面实现的红黑树是K模型的,里面的插入就是按照结点里面的data去比较的
在这里插入图片描述
但是现在map和set都是复用这棵红黑树,所以data里面可能是单独一个key(那就是set实例化的时候),也可能是一个pair(那就对于map)。
那set肯定没问题,因为set对应的就是K模型。
那map的时候呢,map的data里面就是存的pair的键值对,那pair也是可以比较大小,我们直接也说过,所以这里没有报错。
但是pair比较大小的方式是不是不是我们想要的啊。
在这里插入图片描述
而map里面元素的比较我们是不是期望只按照那个key也就是pair.first比较啊

那我们怎么去解决这个问题啊:

那这个地方库里面的做法是比较不错的,我们可以来学习一下
再来看源码里面红黑树的结构
在这里插入图片描述
大家看第三个参数的作用是啥?(后面那个compare就是控制比较方式,大家有兴趣可以自己后面写一下)
这里参数其实就是为了解决这个问题而引入的,它接收一个仿函数。
我们来实现一下,先来看map,我们就可以这样解决:
写一个仿函数取出pair里面的first
在这里插入图片描述
这个仿函数接收一个pair,返回里面的first。
然后对于set,其实是不需要的,但是为了匹配,因为它们用了同一个红黑树模板,所以也要写一个
在这里插入图片描述
然后把这个仿函数传给RBTree的第三个模板参数,这样在红黑树里面,我们不需要管data是单独的key还是pair类型的数据,都可以用一个统一的方式拿到真正用了比较的那个单独的key。
在这里插入图片描述
那涉及到比较的地方都需要我们改一下,我这里就不全部截图出来了,我最后会把完整的代码放出来。
然后大家可以看一下这个图理解一下
在这里插入图片描述
🆗,那这个问题我们就解决了

3.6 红黑树迭代器实现

那接下来我们来实现一下迭代器,实现好之后也可以测试一下我们上面插入之后到底对不对。

迭代器我们重点实现红黑树的,map和set直接复用就行了,库里面源码也是这样搞的。

那红黑树的迭代器呢其实就是结点指针的封装,跟我们之前学的list的迭代器差不多:

那在这里我们控制const迭代器以及重载->这个运算符其实用的方法都和之前list一样,通过增加模板参数去控制,大家遗忘的话可以复习一下list那里相关的实现,下面我就直接写了,有些地方就不过多解释了

那我们先来定义一下它的结构:

里面其实就是封装一个结点的指针在这里插入图片描述

然后常见的几个成员函数实现一下:

在这里插入图片描述

那红黑树的类模板里面这样写就行了:

在这里插入图片描述
上面这些东西都不难,跟之前学的list里面的是一样的,相信大家都能看懂,我就不解释了。

然后我们写一下迭代器的begin和end吧:

首先说一下我们下面的实现和库里面有所不同,库里面其实稍微麻烦一点,我们后面也会提到,但大家按照我们这里讲的实现就行。
那大家想一下,begin返回的是啥?
🆗,在这里是不是应该返回指向中序遍历第一个结点的迭代器啊。
而中序遍历的第一个结点不就是整棵树最左边的那个结点嘛。
在这里插入图片描述
那就是这样在这里插入图片描述
那end呢?
返回中序遍历最后一个结点的下一个:
那最后一个结点后面就没有元素了啊,所以我们直接用空构造一个迭代器返回就行了
在这里插入图片描述

那const版本的begin和end我们也直接写一下:

在这里插入图片描述

另外这里稍微有点挑战的就是++和- -的重载,这两个重载之后我们就可以使用迭代器遍历红黑树了(后面封装好就是遍历map和set),那我们来讲一下:

其实这里相当于我们要搞出一种新的遍历二叉树的方式,之前我们学了递归和利用栈的非递归的方式。
首先++的重载
在这里插入图片描述
大家想一下,最开始迭代器it在1这个结点的位置(它是中序遍历第一个嘛),那怎么样让它++就能走到下一个中序遍历的结点上呢?
🆗,其实不论it当前在那个位置,我们都可以认为它在当前所处的这棵子树的根结点上,那么根结点访问完,中序遍历的话下面访问什么?
跟访问完了,是不是要访问右子树啊。
那对于右子树来说,如果它不为空的话,同样要对它进行中序,所以it下面要访问的就是it的右子树的最左结点
当然要注意这里前提是右子树不为空
在这里插入图片描述
那如果是右为空的情况呢?
在这里插入图片描述
访问完6,然后6的右为空,那下一个是谁?
🆗,大家想,中序遍历是左、根、右嘛。那66访问完了,6的右为空,那是不是相当于6这棵树访问完了。
那6又是1的右子树(左、根、右),那是不是就是1这棵子树访问完了。
然后1的话是它的父亲8的左子树,所以1这棵树访问完就相当于8的左子树访问完了,那下面是不是就要访问根结点8了。
所以如果右为空的话,我们就应该往上去判断it是不是它父亲的左子树,如果不是,就顺着父亲指针往上走,是的话停下来,此时这个父亲就是下一个要访问的结点。
在这里插入图片描述
所以我们这种方法的前提是三叉链,如果不是三叉链的话那就要用我们之前讲的那种借助栈的非递归遍历方法了。
那后续就按照这个逻辑一直走就行了。
在这里插入图片描述
那这样最后一个结点27遍历完,27的右为空,判断它和它父亲的关系,不是父亲的左,继续往上走判断,一直走到根结点13也不符合,然后13的父亲是空,就结束了
所以可以认为end在这个位置
在这里插入图片描述
代码:
在这里插入图片描述
最后返回一下++之后的迭代器。

然后再来搞一下- -(map和set的迭代器是双向迭代器):

那大家思考一下- -要怎么走?
其实就是跟++的反过来就行了,中序是左子树、根、右子树,那- -就右子树、根、左子树就行了
那这样反过来的话就是先判断it的左子树为不为空,不为空就访问左子树的最右结点,如果左为空,那就要向上去找it是谁的右子树,找到的话当前it的这个父亲就是下一个要访问的结点。
在这里插入图片描述

如果大家去看源码的话会发现它的实现跟我们有一些不同:

他给这棵红黑树增加了一个头结点
在这里插入图片描述
头结点的左指针指向最左结点(中序第一个),头结点的右指向最右结点,然后它的parent指向根结点,根结点的parent指向这个头结点
然后其它地方其实跟我们的实现是差不多的。在这里插入图片描述
那他的end就指向这个头结点,就不为空。
大家有兴趣可以看一下它这个实现,但是按我们上面写的就可以了,当然库里面的实现在某些地方会比我们的好一些,我们这样实现的话如果对end–的话其实就会有问题,因为我们的end使用空nullptr构造的,就没法向前寻找前一个结点,因此最好的方式是将end()放在头结点的位置,当然其它位置的是没问题的。
在这里插入图片描述
不过问题不大,我们重点还是学习它的底层框架。

然后反向迭代器大家有兴趣可以自己搞一下,前面我们也讲过怎么弄,这里就不写了。

3.7 封装set和map的迭代器并测试

那红黑树的迭代器实现的差不多了,我们就可以用它封装set和map的迭代器了。

先来搞一下set:

在这里插入图片描述
其实就是在set里面封装一个begin和end(也是去调红黑树的)就行了,set里面的迭代器其实本质就是红黑树迭代器的一个typedef。
那我们运行一下代码看看
在这里插入图片描述
没有问题,是升序。
再来一组
在这里插入图片描述
没问题。
那有了迭代器,就可以用范围for了
在这里插入图片描述

那然后我们再来封装一下map的迭代器并测试一下:

在这里插入图片描述
这就好了
测试一下
在这里插入图片描述
没问题。
然后范围for
在这里插入图片描述
也是可以的。

3.8 map的[]重载

那map与set不同的是不是他还重载了[]啊,这个我们之前在map和set的使用那篇文章也讲过。

那我们接下来就来实现一下map的【】:

那通过前面的学习,我们知道map重载的[],底层的实现是跟insert的返回值有关系的,所以我们得修改一下红黑树的insert
在这里插入图片描述
应该让它返回一个pair:
当插入成功的时候,pair的first为指向新插入元素的迭代器,second为true,当插入失败的时候(其实就是插入的键已经存在了),那它的first为容器中已存在的那个相同的等效键元素的迭代器,second为false。
所以后面这个bool其实是标识插入成功还是失败

这都是之前讲过的。
改造一下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

那map和set里面insert的封装我们也要改一下:

其实把返回值改一下就行了
在这里插入图片描述
在这里插入图片描述

然后就可以给map重载[]了:

在这里插入图片描述
那这就写好了

我们可以用统计次数那个程序测试一下:

在这里插入图片描述
没有问题。

3.9 元素可以修改的问题解决

但是我们上面的实现其实还存在一些问题:

通过前面的学习我们知道set里面的元素是不能修改的,因为底层是搜索树,如果可以随意修改的话就会破坏搜索树的关系。
而对于map,key是不可以修改的,不过value可以修改。
在这里插入图片描述
但我们现在是可以修改的,你看这样他还满足搜索树吗?
就不是了。

那如何解决呢?

我们可以看一下库里面是怎么解决的:
先来看set里面的迭代器
在这里插入图片描述
我们看到它里面的普通迭代器和const迭代器都是用的红黑树的const迭代器。
那我们也这样写呗
在这里插入图片描述
但是现在会有报错
在这里插入图片描述
什么原因呢?
🆗,大家看
在这里插入图片描述
这里_t调用begin,返回的是普通迭代器,但是现在这个返回值iterator是不是红黑树里面const迭代器的typedef,所以这里无法进行类型转换(普通迭代器转换为const迭代器)就出现了问题。
那我们再来看一下库里面怎么解决的:
在这里插入图片描述
不过我们看到库里面并没有在这里做处理。
那其实我们要来看一下红黑树里面的这个地方:
在这里插入图片描述
大家看这里红黑树的迭代器的第三个构造函数。
仔细看一下,是拷贝构造吗?
不是的,大家回忆一下拷贝构造,他要求参数必须是当前类同类型对象的引用
而大家看这里:
在这里插入图片描述
Self才是当前类类型的别名,而第三个构造函数的参数是iterator,跟Self是不一样的,如果它用Self的话那就是拷贝构造了。
这里iterator一定是普通迭代器类型,因为它的后两个参数引用和指针都没有加const,而Self的话如果传过来的Ref和Ptr是普通的引用和指针那它就是普通迭代器,如果是const修饰的那它就是const迭代器。
所以,这里第三个构造函数参数写成iterator的情况下:
如果当前__rb_tree_iterator这个迭代器的类模板被实例化成iterator的话那它就是一个拷贝构造了,因为这是这个参数就跟类对象的类型匹配了;
如果被实例化成这个
在这里插入图片描述
const_iterator的话,那它就是一个可以用iterator去构造const_iterator的构造函数了。
那它这里支持了普通迭代器构造const迭代器的话我们上面提到的那个问题是不是就解决了,因为单参数的构造函数是可以支持隐式类型转换的,这个我们之前学过的。

那大家可能会想既然不能修改那直接让红黑树只实现const迭代器不就行了?

这样对于set是可行的,但是map也要复用这棵红黑树啊,map的key虽然不支持修改,但是value是可以修改的。
所以map是要区分普通迭代器和const迭代器的,不能像set那样普通迭代器和const迭代器都是用的红黑树的const迭代器。

在这里插入图片描述

那现在我们来解决一下:

我们也加一个可以支持普通迭代器构造const迭代器的构造函数就行了
在这里插入图片描述
这样的话不论你__RBTreeIterator被实例化成了普通迭代器还是const迭代器(取决于Ref和Ptr传的是啥),我这里都是用普通迭代器去构造你的(因为参数是<T, T&, T*>
然后再运行
在这里插入图片描述
就不报错了
在这里插入图片描述
而且现在就不能修改了

那map要如何解决Key不能修改,但是value可以修改的问题呢?

那这个我们其实上面已经提到过了
在这里插入图片描述
就是传这个模板参数的时候K用const修饰就行了
我们来试一下
在这里插入图片描述
second即value是可以修改的
在这里插入图片描述
而first即key是不行的。

🆗,那我们的map和set的模拟实现就差不多讲到这里,其它一些我们这里没实现的东西大家有兴趣的可以自己补充,这里我们就不写了。

4. 代码展示

4.1 RBTree.h

#pragma once

enum Colour
{
	RED,
	BLACK,
};

template <class T>
struct RBTreeNode
{
	RBTreeNode<T>* _parent;
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	T _data;
	Colour _col;

	RBTreeNode(const T& data)
		:_parent(nullptr)
		, _left(nullptr)
		, _right(nullptr)
		, _data(data)
		, _col(RED)
	{}

};

template <class T, class Ref, class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;

	Node* _node;

	__RBTreeIterator(Node* node)
		:_node(node)
	{}

	//不论`__RBTreeIterator`被实例化成了普通迭代器还是const迭代器(取决于Ref和Ptr传的是啥)
	//,我这里都是用普通迭代器去构造的(因为参数是`<T, T&, T*>`)
	__RBTreeIterator(const __RBTreeIterator<T, T&, T*>& it)
		:_node(it._node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}

	Self operator++()
	{
		//如果右不为空,下一个访问的是右子树的最左结点
		if (_node->_right)
		{
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}
			_node = subLeft;
		}
		//右为空,往上找it是谁的左子树
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	Self operator--()
	{
		//如果左不为空,下一个访问的是左子树的最右结点
		if (_node->_left)
		{
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}
			_node = subRight;
		}
		//左为空,往上找it是谁的右子树
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
};

template <class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef __RBTreeIterator<T, T&, T*> iterator;
	typedef __RBTreeIterator<T, const T&, const T*> const_iterator;

	iterator begin()
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}

	iterator end()
	{
		return iterator(nullptr);
	}

	//const版本begin和end
	const_iterator begin()const
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return const_iterator(cur);
	}

	const_iterator end()const
	{
		return const_iterator(nullptr);
	}

	pair<iterator,bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true);
		}
		KeyOfT kot;
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kot(data) < kot(cur->_data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(data) > kot(cur->_data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}
		//走到这里cur为空,就是key应该插入的位置
		cur = new Node(data);
		Node* newNode = cur;

		//链接
		if (kot(data) < kot(parent->_data))
			parent->_left = cur;
		if (kot(data) > kot(parent->_data))
			parent->_right = cur;

		//链接父亲指针
		cur->_parent = parent;

		//如果插入之后它的parent是红的,就需要进行调整
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			//如果父亲是祖父的左孩子,那右孩子就是叔叔
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//这里处理的情况是叔叔存在且为红,变色+向上继续处理
				if (uncle && uncle->_col == RED)
				{
					//将p,u改为黑,g改为红
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					//更新cur为grandfather,判断它的父亲是什么情况:
					//1.如果不存在或者为黑,就需要继续处理了,也不会进行循环了
					//2.如果它的父亲存在且为红,重新循环进行调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else//叔叔不存在/叔叔存在且为黑的情况
				{
					//     g
					//   p   u
					// c 
					if (cur == parent->_left)//左左——右单旋+变色
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//左右——左右双旋+变色
					{
						//     g
						//   p   u
						//     c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					//这里调整完就可以break了,因为颜色都变的合适了,而相关的链接关系旋转会帮我们处理
					break;
				}
			}
			//如果父亲是祖父的右孩子,那左孩子就是叔叔
			else //parent = grandfather->_right
			{
				Node* uncle = grandfather->_left;
				//这里处理的情况是叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					//将p,u改为黑,g改为红
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					//更新cur为grandfather,判断它的父亲是什么情况:
					//1.如果不存在或者为黑,就需要继续处理了,也不会进行循环了
					//2.如果它的父亲存在且为红,重新循环进行调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)//右右——左单旋+变色
					{
						//    g
						//  u   p
						//        c
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//右左——右左双旋+变色
					{
						//    g
						//  u   p
						//    c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		//上面处理过程中有可能会把根变成红色,这里统一处理一下,把根置成黑
		_root->_col = BLACK;
		return make_pair(iterator(newNode), true);
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
	bool IsBlance()
	{
		if (_root && _root->_col == RED)
		{
			cout << "根结点是红色" << endl;
			return false;
		}

		//先求出一条路径黑色结点数量
		int mark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				++mark;
			cur = cur->_left;
		}

		//检查是否出现连续红色结点及所有路径黑色结点数量是否相等
		return _Check(_root, 0, mark);
	}
	int TreeHeight()
	{
		return _TreeHeight(_root);
	}
	Node* find(const T& data)
	{
		KeyOfT kot;
		Node* cur = _root;
		while (cur)
		{
			if (kot(data) < kot(cur->_data))
			{
				cur = cur->_left;
			}
			else if (kot(data) > kot(cur->_data))
			{
				cur = cur->_right;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}
private:
	void _Destroy(Node* root)
	{
		if (root == nullptr)
			return;

		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}
	int _TreeHeight(Node* root)
	{
		if (root == nullptr)
			return 0;
		int RightH = _TreeHeight(root->_left);
		int leftH = _TreeHeight(root->_right);
		return RightH > leftH ? RightH + 1 : leftH + 1;
	}
	bool _Check(Node* root, int blackNum, int mark)
	{
		if (root == nullptr)
		{
			//走到空就是一条路径走完了,比较一下是否相等
			if (blackNum != mark)
			{
				cout << "存在黑色结点数量不相等" << endl;
				return false;
			}
			return true;
		}

		if (root->_col == BLACK)
			++blackNum;

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "出现连续红色结点" << endl;
			return false;
		}

		return _Check(root->_left, blackNum, mark)
			&& _Check(root->_left, blackNum, mark);
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_data << " ";
		_InOrder(root->_right);
	}
	//左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		//旋转并更新_parent指针
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		//先保存一下parent->_parent,因为下面会改它
		Node* pparent = parent->_parent;

		//旋转并更新_parent指针
		subR->_left = parent;
		parent->_parent = subR;

		//若pparent为空则证明旋转的是一整棵树,因为根结点的_parent为空
		if (pparent == nullptr)
		{
			//subR是新的根
			_root = subR;
			_root->_parent = nullptr;
		}
		//若pparent不为空,则证明旋转的是子树,parent上面还有结点
		else
		{
			//让pparent指向子树旋转之后新的根
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->_right = subR;
			}
			//同时也让新的根指向pparent
			subR->_parent = pparent;
		}
	}

	//右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		//旋转并更新_parent指针
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		//先保存一下parent->_parent,因为下面会改它
		Node* pparent = parent->_parent;

		//旋转并更新_parent指针
		subL->_right = parent;
		parent->_parent = subL;

		//若parent等于_root则证明旋转的是一整棵树(这也是一种判断方法)
		if (parent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			//让pparent指向子树旋转之后新的根
			if (parent == pparent->_left)
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
			//同时也让新的根指向pparent
			subL->_parent = pparent;
		}
	}

private:
	Node* _root = nullptr;
};

4.2 Map.h

#pragma once

#include "RBTree.h"

namespace yin
{
	template <class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;

		iterator begin()
		{
			return _t.begin();
		}

		iterator end()
		{
			return _t.end();
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
			return ret.first->second;
		}

		pair<iterator,bool> insert(const pair<const K, V>& kv)
		{
			return _t.Insert(kv);
		}
	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};
	void test_map1()
	{
		map<string, string> dict;
		dict.insert(make_pair("sort", "排序"));
		dict.insert(make_pair("left", "左边"));
		dict.insert(make_pair("right", "右边"));
		dict.insert(make_pair("string", "字符串"));
		dict.insert(make_pair("insert", "插入"));
		dict.insert(make_pair("erase", "删除"));
		dict.insert(make_pair("erase", "删除"));


		map<string, string>::iterator it = dict.begin();
		while (it != dict.end())
		{
			cout << it->first << ":" << it->second << endl;
			++it;
		}
		cout << endl;

		/*for (auto e : dict)
		{
			cout << e.first << ":" << e.second << endl;
		}*/
	}
	void test_map2()
	{
		string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };

		map<string, int> m;
		for (auto e : arr)
		{
			m[e]++;
		}

		for (auto e : m)
		{
			cout << e.first << ":" << e.second << endl;
		}
	}
}

4.3 Set.h

#pragma once

#include "RBTree.h"

namespace yin
{
	template <class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

		iterator begin()
		{
			return _t.begin();
		}

		iterator end()
		{
			return _t.end();
		}

		pair<iterator,bool> insert(const K& key)
		{
			return _t.Insert(key);
		}
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};

	void test_set1()
	{
		int arr[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
		set<int> s1;
		for (auto e : arr)
		{
			s1.insert(e);
		}

		set<int>::iterator it = s1.begin();
		while (it != s1.end())
		{
			cout << *it << " ";
			++it;
		}
		/*for (auto e : s1)
		{
			cout << e << " ";
		}*/
		cout << endl;
	}
}

4.4 Test.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include "Map.h"
#include "Set.h"
#include <set>

int main()
{
	//yin::test_set1();
	yin::test_map1();
	return 0;
}

在这里插入图片描述

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

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

相关文章

uniapp scroll-view横向滚动无效,scroll-view子元素flex布局不生效

要素排查&#xff1a; 1.scroll-x属性需要开启&#xff0c;官方类型是Boolean&#xff0c;实际字符串也行。 2scroll-view标签需要给予一个固定宽度&#xff0c;可以是百分百也可以是固定宽度或者100vw。 3.子元素需要设置display: inline-block&#xff08;行内块元素&#x…

国标GB28181视频平台EasyGBS通过对应密钥上传到其他平台展示的详细步骤来啦!

国标GB28181协议视频平台EasyGBS是基于国标GB28181协议的视频云服务平台&#xff0c;支持多路设备同时接入&#xff0c;并对多平台、多终端分发出RTSP、RTMP、FLV、HLS、WebRTC等格式的视频流。平台可提供视频监控直播、云端录像、云存储、检索回放、智能告警、语音对讲、平台级…

项目管理实战笔记:硬技能(下)

需求变更&#xff1a; pmp里面涉及需求变更的很多&#xff0c;尤其是CCB 对于CCB的需求变更&#xff0c;可能需要以下步骤&#xff1a; 1. 确定变更的原因和必要性&#xff1a;需要了解变更是因为业务需求的变化、技术方案的变更还是其他原因&#xff0c;以及变更是否是必要的…

内行才知道的新闻稿发布技巧,媒介盒子为你揭秘

不知道最近其他小伙伴有没有遇上以上说的一些情况&#xff0c;必须借助新闻稿发布达到营销推广的目的。 可是对于新闻稿发布这一块却不知道应该怎么办&#xff0c;尤其是新手上任&#xff0c;对于如何发布新闻稿是一头雾水。 新闻稿的发布是企业在公关传播中重要的一环&#…

如何将PDF文件转换为PPT文件?

如何将pdf转换成ppt&#xff1f;PDF文件作为常用的文件格式&#xff0c;不仅可以在教学过程中使用&#xff0c;还可以在营销展会、培训讲座等过程中使用。欧迪芬文件的使用&#xff0c;能够在一定程度上提升我们的办公效率。对于PDF文件来说&#xff0c;其中包含的元素非常多&a…

【算法刷题之数组篇(2)】

目录 1.leetcode-35. 搜索插入位置&#xff08;简单&#xff09;2.leetcode-74. 搜索二维矩阵&#xff08;中等&#xff09;3.leetcode-73. 矩阵置零&#xff08;中等&#xff09;4.leetcode-56. 合并区间&#xff08;中等&#xff09;5.leetcode-54. 螺旋矩阵&#xff08;中等…

水溶性试剂Hexynoic acid STP ester,炔酸STP酯,NHS酯的极好替代品

产品简介&#xff1a;炔酸STP酯是一种具有末端炔丙基和STP酯基的水溶性试剂。STP酯是在水性环境中进行偶联反应的传统N-羟基琥珀酰亚胺&#xff08;NHS&#xff09;酯的极好替代品。炔丙基通过铜催化的叠氮化物-炔烃点击化学与含叠氮化物的化合物或生物分子反应&#xff0c;产生…

leetcode303. 区域和检索 - 数组不可变(java)

前缀和数组的应用 区域和检索 - 数组不可变题目描述前缀和数组代码演示 区域和检索 - 数组不可变 难度 - 简单 原题链接 - 区域和检索 - 数组不可变 题目描述 给定一个整数数组 nums&#xff0c;处理以下类型的多个查询: 计算索引 left 和 right &#xff08;包含 left 和 righ…

解决运行在微信小程序中报[ app.json 文件内容错误] app.json: app.json 未找到(env: Windows,mp,1.05.2204

找到project.config.json文件夹 添加 "miniprogramRoot": "unpackage/dist/dev/mp-weixin/", 即可

发展全球电商业务,为什么首选Facebook Messenger电子商务?

Facebook Messenger电子商务就是使用Facebook的消息传递渠道Messenger来购买和销售产品或服务。通过将 Messenger与客户服务平台&#xff0c;例如SaleSmartly&#xff08;SS客服&#xff09;集成&#xff0c;企业可以利用渠道强大的消息传递功能为客户提供更加个性化和对话式的…

这个免费全景网站你知道吗,多篇教程教您如何创作全景作品

引言&#xff1a; 随着科技的不断进步&#xff0c;虚拟现实&#xff08;VR&#xff09;技术正逐渐融入我们的日常生活&#xff0c;为我们带来了没有过的沉浸式体验。其中&#xff0c;VR全景网站作为一种引领人们身临其境体验的创新方式&#xff0c;正在逐渐受到大众的关注。 蛙…

androidStudio或IDEA的通过gitBash打开插件

本人&#xff0c;一个资深的命令行&#xff0c;业余爱好者。常年直接vim&#xff0c;或者shell上服务器阅读代码。比较偏好使用GitBash来打开项目&#xff0c;进行git status&#xff0c;git diff&#xff0c;git add&#xff0c;commit等动作。 基于以上原因&#xff0c;本人开…

怎样通过本地电脑搭建SFTP服务器,并实现公网访问?

本地电脑搭建SFTP服务器&#xff0c;并实现公网访问 文章目录 本地电脑搭建SFTP服务器&#xff0c;并实现公网访问1. 搭建SFTP服务器1.1 下载 freesshd 服务器软件1.3 启动SFTP服务1.4 添加用户1.5 保存所有配置 2. 安装SFTP客户端FileZilla测试2.1 配置一个本地SFTP站点2.2 内…

SecureCRT 备份Button Bar中所有Button

一、前言 Button Bar功能可以保存一些常用命令避免重复输入&#xff0c;但是有时候secureCRT的button bar经常莫名其妙消失&#xff0c;重装系统或软件后&#xff0c;也都需要重新一个个添加Button&#xff0c;如果能备份就能减少这些费时间的操作 二、备份步骤 在面板Optio…

江西南昌电气机械三维测量仪机械零件3d扫描-CASAIM中科广电

精密机械零部件是指机械设备中起到特定功能的零件&#xff0c;其制造精度要求非常高。这些零部件通常由金属、塑料或陶瓷等材料制成&#xff0c;常见的精密机械零部件包括齿轮、轴承、螺丝、活塞、阀门等。精密机械零部件的制造需要高精度的加工设备和工艺&#xff0c;以确保其…

我们可能要为ChatGPT的谢幕做好准备

ChatGPT的未来&#xff1a;悬念仍存 ​ 人工智能已经成为我们生活不可或缺的一部分。在众多AI应用中&#xff0c;OpenAI研发的ChatGPT凭借其极强的语言理解和生成能力脱颖而出&#xff0c;是一项划时代的变革性创新&#xff0c;帮助了无数企业和个人&#xff0c;改变了我们与技…

【Apollo】自动驾驶感知——毫米波雷达

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 七七的闲谈 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f…

Java课题笔记~JSON

3.1 概述 概念&#xff1a;JavaScript Object Notation。JavaScript 对象表示法. 如下是 JavaScript 对象的定义格式&#xff1a; {name:"zhangsan",age:23,city:"北京" } 接下来我们再看看 JSON 的格式&#xff1a; {"name":"zhangsa…

「网页开发|环境安装」Windows系统下安装node.js

本文主要介绍在windows系统下的node.js环境安装。windows系统的Node.js安装过程与其他普通软件的安装类似&#xff0c;本文主要给刚入门的伙伴一个参考。 文章目录 场景说明安装步骤下载安装包运行安装程序验证安装添加系统环境变量配置node_cache和node_global变量 场景说明 …

【HCIP】01.RSTP

STP的缺点 STP对计时器的依赖&#xff08;需要等固定30s&#xff09;STP重收敛速度过程慢&#xff08;30s或50s&#xff09;拓扑变化机制太慢&#xff08;高度中央集权&#xff0c;需要上报到根桥再下发TCN&#xff09; RSTP 802.1W&#xff0c;收敛速度更快&#xff0c;能够…