《红黑树大能量——set,map封装模拟》


前言

书接上篇博客《手撕红黑树》,我们学习到了红黑树是如何通过其内部方式来管理数据的,本篇文章将基于上篇文章继续探讨红黑树的更多应用。set,map是STL库中很重要的容器,他们就是利用红黑树作为底层来实现的,今天让我们一起走进set与map的世界,通过模拟的方式理解其设计,揭开这两位容器的神秘面纱。

目录

前言

一、STL源码中的设计

1、bool值代替颜色

2、采用相同红黑树模板

3、红黑树结构中的泛型节点

4、红黑树的一个模板参数

5、红黑树的其他模板参数

二、map、set模拟封装

1、红黑树改造

(1)、红黑树节点改造

(2)红黑树节点比较

(3)、红黑树第一个模板参数

(4)、红黑树迭代器

1、迭代器结构

2、begin()与end()

3、operator++()

4、operator--()

5、迭代器的其他操作

6、const迭代器的完善

(5)、insert返回值

2、map与set封装

1、红黑树实例化参数

2、红黑树节点比较

3、map,set迭代器

4、set、map对其他函数的封装

5、map方括号【】

结束语

附录

改造后的红黑树类模板

set封装代码

map封装代码

测试代码


一、STL源码中的设计

Set结构设计

Map结构设计

上图重点已用红色框图圈出

我们可以发现对于map和set来说,他们在STL库中的实现底层都是红黑树,这也证明了红黑树应用更加多的一个说法,下面我们具体来分析一下STL中利用红黑树对map与set实现的细节。

1、bool值代替颜色

库中采用bool值来代表红黑树的颜色,红色则为1,黑色则为0

2、采用相同红黑树模板

首先明确一点,我们希望利用红黑树来管理不同类型的数据,于是将红黑树写成泛型,适配各种类型数据。将红黑树写成模板,以管理不同类型的红黑树

set是一个k(key)结构,而map是一个kv(key,value)结构,按道理来说set与map容器的实现应该是k,kv两个不同模式的红黑树来实现(也就是不同的红黑树模板),可STL中用的是同一个红黑树模板。仔细观察不难发现,在map中向红黑树模板传递参数时,传递key类型,pair类型,实例化的红黑树为(key,pair)。但在set中向红黑树模板传递参数时,第一个与第二个参数相同,都是key的类型,也就是再传value的位置仍然传递了key的类型,实例化的红黑树为(key,key)。大家在这里记住这个设计,这样设计的原因大家继续往下看就会发现(利用第二个模板参数构造不同结构下的红黑树节点)。

3、红黑树结构中的泛型节点

当map与set都使用复用同一个红黑树模板,该如何使编译器区分这两种结构,并产生不同结构的红黑树节点成了一个艰巨的任务。

STL将红黑树的节点设计为一个模板类型(泛型)(根据传入类型而实例化不同类型的节点),利用红黑树模板参数中的value类型来进行实例化,value类型决定节点里面存什么,map与set的红黑树模板value接收的类型不同最终使得生成的节点类型不同,从而达到使编译器区分这两种结构,是key类型的set,还是(key,value)类型的map,并产生不同结构的红黑树节点。

下图时STL库中的map,set模板参数传递实例化红黑树及红黑树节点的流程展开图

对于在源码中存在很多typedef,可能看起来不太直观,我们将其流程化简,再来看一下map与set复用同一个红黑树模板的流程

利用向红黑树模板传递的第二个参数来构建红黑树节点,是key类型的就是set结构,是pair类型就是map结构。

4、红黑树的一个模板参数

既然我们用了红黑树模板的第二个模板参数来区分是map(kv)还是set(v),而且节点存放的类型也是由第二个模板参数决定的,那么对于第一个看似多余的key类型传递给红黑树有什么必要吗?

答案是非常有必要的,比如红黑树(kv)模式中,查找函数是利用key值查找value值,如果我们只有pair类型,没有key类型,那么如何接收向这个函数传递的参数呢?是不行,所以我们必须要有key值类型这个模板参数,来定义形参接收key值类型的变量。

甚至在有的时候我们要在kv模式下提取pair中的key值,需要有与key类型相同的变量进行接收,都会用到key值类型的模板参数。在这里做初步介绍,在后面部分还会给大家强调第一个模板参数的重大作用,要记住的就是,第一个模板参数不多于,且很重要!!!

5、红黑树的其他模板参数

**KeyOfValue:**如果这个树是kv结构的树,那么Value参数为pair,KeyOfVal是一个仿函数,作用就是提取value类型对象中的key类型的元素。

Compare也是一个仿函数,提供红黑树管理某一类型数据时,这类型数据的比较逻辑。

后续模拟实现中,也会实现这两个模板参数,大家可以根据实际模拟更直观清楚的知道其具体作用,并且在使用时能够得心应手传递对应仿函数

二、map、set模拟封装

根据上述源码细节剖析,有些内容还是晦涩难懂。为了理解其细节更加深刻与透彻,依据STL中对set,map封装理念,根据之前博客《手撕红黑树》中的模拟红黑树为基础,实现对set,map的简单模拟封装,一步一步身临其境的体会其设计细节

1、红黑树改造

我们在这里用T表示红黑树的第二个模板参数!!!!!

(1)、红黑树节点改造

map,set模板参数传递实例化红黑树及红黑树节点的流程展开图在STL分析部分已近展示,由于在源码中存在很多typedef,可能看起来不太直观,我们将其流程化简,再来看一下map与set复用同一个红黑树模板的流程。

利用向红黑树模板传递的第二个参数来构建红黑树节点,是key类型的就是set结构,是pair类型就是map结构。

利用同一个红黑树模板,根据模板参数的不同,构造出不同的节点,成为不同的结构,这就是我们需要改造节点的原因与目的。

下图原来的红黑树节点模板:

我们可以看到原来节点中的存储的数据类型,是利用节点模板的第一个模板参数和第二个模板参数实例化的pair类型对象,这是适用map的红黑树(kv结构)。对于set来说节点存储的数据类型就是k类型,没有v类型数据,更没有pair类型对象。这样就要单独写一个符合k结构的红黑树模板类,创建其独特的节点模板,实例化对应的节点来存储期望类型的数据(即存储k类型的数据),之后才能实例化出k结构的对象。(k结构的节点模板是单模板参数,kv结构的节点模板是两个模板参数)

为了利用同一个红黑树模板,就要在红黑树中使用单模板参数的泛型节点,利用实例化红黑树的第二个模板参数实例化节点,从而使得参数不同,红黑树的节点结构不同(适配不同结构k或者kv结构下的类型数据)。如上面的流程图,map实例化红黑树的模板参数为(k,pair),那么就是用pair类型来实例化红黑树节点,set实例化红黑树的模板参数为(k,k),那么就是用k类型来实例化红黑树节点。综上所述,就是利用红黑树模板的第二个模板参数来实例化节点,使用第二个模板参数为存储数据的类型。如下图改造后的红黑树节点

可以发现数据类型不在是原来固定的pair<k,v>,而是T类型,代表的可能是kv结构下红黑树传递的pair<k,v>类型,也有可能是k结构下红黑树传递的k类型。实现对红黑树模板传参不同,以达到实现不同结构红黑树的效果。

(2)红黑树节点比较

红黑树的底层为搜索二叉树,所以构建,及调整都是需要比较节点的k值大小的。对于节点结构的改变意味着我们的节点比较逻辑也要发生改变。

我们看到原来节点的数据是pair类型对象,那么我们只需要比较该键值对对象的的第一个元素就可以了(即_kv.first),但现在新的节点中数据类型为T。如果 T代表的是k类型(k结构),那么我们利用k类型的比较逻辑直接比较T类型的两个节点数据就可以了。但如果T代表的是pair(kv结构),我们则无法直接比较T类型的数据 。因为pair的原比较逻辑是先按照键值对中的第一个元素比较,再按照第二个元素来比较,对于kv结构来说就是先按照k类型数据比较,再按照v类型数据比较。但我们实际对kv结构下的节点比较,期望是只按照k类型的数据来比较,此刻T的默认比较逻辑与实际需求不符,所以我们就要想办法改造他的比较逻辑

这里采用的方法是利用仿函数取出T类型对象中k类型元素,然后再依据k类型的比较逻辑对k对象比较结果作为红黑树节点的比较结果。

上图就是利**用红黑树的第三个模板参数KeyofT,创造该类型对象,借助其内部逻辑取出T类型对象中的k对象,再来比较。**对于k结构来说,T代表的就是k,返回的就是T对象本身;对于kv结构来说,T代表的是pair,返回的就是T对象中的k类型对象。比较提取后的返回值。在红黑树中,所有涉及比较节点的代码部分都要修改,添加对节点数据的提取操作;

kv结构(map)与k结构(set)的提取逻辑不同,向红黑树传递的模板参数(仿函数)也不同,我们在后面对map,set的封装中会更加直观的看到。

(3)、红黑树第一个模板参数

第一个模板参数不多于,且很重要!!!

1、我们无法利用第三个模板参数,就是仿函数KeyOfT,取出T类型中的T类型,所以在需要创建或者接受k类型对象时要知道其类型,实例化时要把该类型作为模板参数传递过来。

2、在find中,无论哪种结构都是利用k类型对象来查找,利用到红黑树模板的第一个参数,也就是k类型,作为参数类型定义形参接收k类型对象,完成查找。

(4)、红黑树迭代器

迭代器是以统一的形式,屏蔽不同的底层结构细节,提供统一的访问格式,让我们使用,访问和修改容器数据的难度降低,成本降低。而红黑树的迭代器就是屏蔽红黑树的底层细节,来实现红黑树数据访问,遍历,修改。

1、迭代器结构

**红黑树的迭代器本质就是对红黑树节点的封装。**如下图可以看到我们红黑树结构的一个模拟,依旧是一个模板,适应不同的红黑树类型。**模板参数T是红黑树节存储数据的类型;Ref是重载迭代器重载运算符(*)解引用的返回值类型,是对存储数据的引用类型;Pre是重载运算符(->)箭头的返回值类型,是数据地址的类型。**在后面模拟这两个函数的时候也会和大家介绍。

红黑树中的迭代器实例化类型

2、begin()与end()

STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?能否给成nullptr呢?答案是行不通的 ,因为对end()位置的迭代器进行--操作,必须要能找最后一个元素,此处就不行。这里先保留疑问,关于如何处理在后面讲解。

下图是红黑树中begin()的模拟实现代码。

3、operator++()

begin我们知道了它是红黑树中最小节点,也就是最左节点,因为不考虑operator--的实现问题,所以不需要end()位置的迭代器进行--操作,必须要能找最后一个元素,我们就让end()迭代器指向空节点,那么我们先提供只适合++实现的end()的代码实现

解决方法:

情况一:迭代器指向节点的右边不为空

++后的下一个节点就是右子树的最左节点

情况二:迭代器指向节点的右边为空

说明这颗子树的中序访问完了,这时候向上不断找,下一个节点是祖先里面,孩子是父亲左的那个父亲节点,这也作为循环的退出条件。改变迭代器中的节点指针,指向parent节点;

细节:

1、在第二种情况处理办法中,最后如果一直没有找到符合退出循环条件的节点,也就是当父亲节点(parent)变成空,说明找到了根节点,返回nullptr,即 end();

2、在++过程中改变的是迭代器对象中的指针,最终返回当前对象,即*this

4、operator--()

处理情况与处理方法都与operator++相似,只是处理方向不同。但要注意此刻我们end()的迭代器中的节点指针是nullptr,对end()位置的迭代器进行--操作,必须要能找最后一个元素,要进行特殊处理。

情况一:迭代器指向节点的左边不为空

--后的下一个节点就是左子树的最右节点

情况二:迭代器指向节点的左边为空

说明这颗子树的中序访问完了,这时候向上不断找,下一个节点是祖先里面,孩子是父亲右的那个父亲节点,这也作为循环的退出条件。改变迭代器中的节点指针,指向parent节点;

情况三:end()--

阶段一

在这里我们使用的end()的迭代器中的节点指针是nullptr,我们做一个特殊处理,希望--以后必须要能找最后一个元素。特殊处理就是如果迭代器指向空,那么--操作后就让迭代器指向整棵树的一个最右边节点;。这种解决办法有两个缺陷

一、是最右边节点找起来很麻烦,此刻迭代器指针指向nullptr就不能采用向上查找的方法找到最祖先节点,再找最右边节点;这就需要我们提前存储一份红黑树最右节点的指针。增加对应的特殊处理办法。

二、对于空树无法处理。

阶段二

此时我们采取一种新的解决方式------增加红黑树哨兵位节点这也是上文中保留的疑问的解决。

哨兵位节点颜色是红色的,其左右节点分别链接红黑树的最左节点与最右节点,这样就可以直接方便的找到最左节点与最右节点。哨兵位节点为end()迭代器指向节点,就可以实现end()--操作,直接找到最右节点。

补充:对于是哨兵位节点的判断(判断是end()指向的节点)

在本次模拟中,增加哨兵位节点对整个红黑树改变较大,本次没有对此方案进行模拟,大家可以下去之后自己尝试。

5、迭代器的其他操作

解引用操作符

箭头操作符

等于操作符

不等于操作符

6、const迭代器的完善

迭代器是一个模板,只要传递const类型的模板参数给迭代器实例化,那么迭代器中的数据无论如何都不会被修改。

当然还需要提供const版本的begin(),end()

(5)、insert返回值

为支持map方括号操作,将insert()的返回值类型修改成pair<iterator,bool>;

返回值规则

1、如果插入值已经有了,说明插入失败,就返回pair<指向已有节点的红黑树迭代器,false>

2、如果插入值不存在,说明插入成功,就返回pair<指向新插入节点的红黑树迭代器,true>

2、map与set封装

1、红黑树实例化参数

set

实例化化红黑树对象为成员变量(封装)

红黑树节点

和之前对STL中分析的一样,由于k结构,kv结构都是用同一个红黑树模板,第二个参数是区分和构建不同模式的关键,用来构造k结构下的节点及数据。于是set对红黑树的封装实例化,第一个模板参数和第二个模板参数都为k类型。

set中希望数据不能被修改,也就是红黑树中的数据不能修改。由于构建红黑树节点的是红黑树的第二个模板参数,所以将红黑树的第二个模板参数添加const关键字,保证红黑树中的数据不能修改,满足set要求。(防止使用对应迭代器时,数据可以被修改)

map

实例化化红黑树对象为成员变量(封装)

红黑树节点

和之前对STL中分析的一样,由于k结构,kv结构都是用同一个红黑树模板,第二个参数是区分和构建不同模式的关键,用来构造kv结构下的节点及数据。于是map对红黑树的封装实例化,第一个模板参数为k,第二个模板参数为pair类型。

map中希望键值对的键(键值对的第一个元素)不能被修改,也就是kv结构下红黑树中的k数据不能修改。由于构建红黑树节点的是红黑树的第二个模板参数,所以将红黑树的第二个模板参数pair的第一个模板参数添加const关键字,保证红黑树中的键数据不能修改,值可以修改,满足map要求。(防止使用对应迭代器时,数据可以被修改)

2、红黑树节点比较

我们在上面看到set,map对于红黑树的封装还有第三个模板参数,第三个模板参数是一个仿函数,借助其内部逻辑取出T类型对象中的k类型对象,对k类型对象进行比较。

对于set来说,节点的模板参数T代表的就是k,仿函数的重载()返回值的就是T类型对象本身(添加const,保证其值是不被修改的) ;对于仿函数的参数,其类型就是实例化set的模板参数k(添加const,保证其值是不被修改的) ,固将仿函数写为容器的内部类,在这里就是set的内部类;所以为了具体仿函数如下图:

对于map来说,节点的模板参数T代表的就是pair,仿函数的重载()返回值的就是T类型对象中的第一个元素,也就是const k类型的元素 ;对于仿函数的参数,其类型就是实例化map的模板参数k,v构成的键值对类型(添加const关键字,保证其值是不被修改的),固将仿函数写为容器的内部类,在这里就是map的内部类;所以为了具体仿函数如下图:

在使用时创建仿函数对象,利用其内部逻辑取出红黑树数据中的k类型数据,再利用k类型数据的比较逻辑比较k类型的数据,作为红黑树数据的比较结果。

3、map,set迭代器

map,set迭代器,仍然是对其对应实例化出来的的红黑树的迭代器的封装

set迭代器

对应实例化红黑树中迭代器的名称统一(普通迭代器与const迭代器)

注意:类模板里取东西,编译器不知道是类型还是静态成员变量,借助typename告诉编译器是类型

对红黑树对象中迭代器函数的封装调用(普通迭代器与const迭代器)

map迭代器

对应实例化红黑树中迭代器的名称统一(普通迭代器与const迭代器)

对红黑树对象中迭代器函数的封装调用(普通迭代器与const迭代器)

4、set、map对其他函数的封装

1)、插入函数:

set

map

1、各自插入函数的参数类型与各自结果存储数据的类型相同

2、封装后的返回值与对应于红黑树插入返回值相同

2)、查找函数

(set,map相同,都是利用k类型对象查找)

5、map方括号【】

利用插入函数的返回值来完成。

参数为k类型元素,先按照k值向红黑树插入,再提取插入得到的返回值;插入成功返回值是指向新节点的迭代器+true,插入失败说明已存在该数据,返回值的是指向已有节点的迭代器+false。**而方括号的返回值是对提取得到的迭代器解引用后的的pair对象的第二个元素的引用。**也就是说当你对返回值进行修改操作,也就意味着直接改变迭代器指向节点所存储的键值对的值(value)。

满足以下功能:

1、k类型元素不存在,插入pair键值对,并且修改键值对的v类型对象。

2、k类型元素存在,修改已有键值对中的v类型对象。

利用统计水果个数测试方括号功能:

结束语

本篇文章的内容就到此结束了,希望通过这篇博客大家能够更深刻的理解set与map的底层设计,对红黑树这一结构更加熟悉能够运用自如。对于这方面大家有什么内容不明白的,可以在评论去向我提问,我会一一回答,当然有什么错误或者有什么不足的地方,希望大家可以包容并指出,后续还会更新c++的其他知识,希望大家可以持续关注,向每一位读者送上真诚的小花。

附录

改造后的红黑树类模板

cpp 复制代码
#pragma once

namespace zwb
{

    enum Color { RED, BLACK };

    //改造红黑树类模板,使mymap,myset适用同一个红黑树类模板
    template <class T>
    struct RBTreeNode {
        typedef RBTreeNode<T> Node;
        Node* _parent;
        Node* _left;
        Node* _right;
        Color _col;
        T _data;

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


    //红黑树迭代器
    template<class T,class Ref,class Ptr>    
    struct RBTreeIterator
    {

        typedef RBTreeNode<T> Node;
        typedef RBTreeIterator<T,Ref,Ptr> self;
       

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

        Ref operator*()
        {
            return _node->_data;
        }
        Ptr operator->()
        {
            //利用箭头取pair中的first,或者second,此处有特殊处理,有一个箭头被省略了((&pair)->first/second)
            return &_node->_data;
        }

        //迭代器++操作
        self& operator++()
        {
            //1、迭代器指向节点的右边不为空,下一个节点就是右子树的最左节点
            if (_node->_right)
            {
                Node* subleft= _node->_right;
                while (subleft->_left)
                {
                    subleft = subleft->_left;
                }
                _node = subleft;
            }
            //2、迭代器指向节点的右边为空,说明颗子树的中序访问完了
            //向上不断找,下一个节点是祖先里面,孩子==父亲左的那个父亲节点
            //当parent变成空,说明找到了根节点,返回nullptr,即 end()
            else
            {
                Node* cur = _node;
                Node* parent = cur->_parent;
                while (parent && cur == parent->_right)
                {
                    cur = parent;
                    parent = cur->_parent;
                }
                _node = parent;
                
            }
            return *this;
        }
        //封装节点指针
        
        //迭代器--操作
        self& operator--()
        {
            //特殊处理end()节点--操作
            if (_node == nullptr)
                return *this;
            //1、迭代器指向节点的左边不为空,下一个节点就是左子树的最右节点
            if (_node->_left)
            {
                Node* subr = _node->_left;
                while (subr->_right)
                {
                    subr = subr->_right;
                }
                _node = subr;
            }
            //2、迭代器指向节点的左边为空,说明颗子树的中序访问完了
            //向上不断找,下一个节点是祖先里面,孩子==父亲右边的那个祖先节点
            //当parent变成空,说明找到了根节点,返回nullptr,即 rend()
            else
            {
                Node* cur = _node;
                Node* parent = cur->_parent;
                while (parent && cur == parent->_left)
                {
                    cur = parent;
                    parent = cur->_parent;
                }
                _node = parent;
            }
            return *this;
        }

        bool operator!=(const self& it)
        {
            return _node != it._node;
        }

        bool operator==(const self& it)
        {
            return _node == it._node;
        }

        Node* _node=nullptr;
    };


    //需要提供将key值从_data中提取出来的的仿函数keyofT

    //map中对应的数据类型是pair,pair支持的比较是先按照first比较,如果first比较失败,则会再根据second来比较
    //预期是只用first比较,与逾期不符,利用keyofT从T中取出key,再进行比较
    //更新所有的比较方式

    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_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);
        }



        //为支持map方括号操作,这里的返回值修改成pair<iterator,bool>
        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->_right;
                }
                else if (kot(data) < kot(cur->_data))
                {
                    parent = cur;
                    cur = cur->_left;
                }
                else
                    return make_pair(iterator(cur),false);
            }
            cur = new Node(data);
            //要返回新插节点的迭代器,提前保存节点
            Node* newnode = cur;
            if (kot(data) < kot(parent->_data))
            {
                parent->_left = cur;
            }
            else
            {
                parent->_right = cur;
            }
            cur->_parent = parent;



            Node* grandfather = nullptr;
            Node* uncle = nullptr;
            while (parent && parent->_col == RED)
            {
                //父亲节点为红色,有连续红色,需要特殊处理,此时一定存在爷爷节点
                grandfather = parent->_parent;
                //一、parent在grandfather的左边,说明uncle在右边
                if (parent == grandfather->_left)
                {
                    uncle = grandfather->_right;

                    //1、uncle存在,且uncle为红色
                    //parent,uncle变黑色,grandfather变红色,并且继续向上处理
                    //*********   不关心uncle的方向   *******
                    if (uncle && uncle->_col == RED)
                    {
                        parent->_col = uncle->_col = BLACK;
                        grandfather->_col = RED;

                        //需要继续向上处理,直到退出循环;
                        cur = grandfather;
                        parent = cur->_parent;
                    }
                    //2、uncle不存在,或者uncle存在且为黑色,        利用旋转的方式处理,
                    //**********   uncle左右位置不同,处理方式不同
                    else
                    {
                        //(1)cur是parent的左边,右旋grandfather,
                        // 变色:p变黑,g变红
                        //          g
                        //       p     u
                        //     c
                        if (cur == parent->_left)
                        {
                            RotateR(grandfather);
                            grandfather->_col = RED;
                            parent->_col = BLACK;
                        }

                        //(2)cur是parent的有右边,左旋parent,右旋grandfather
                        //变色: c变黑,g变红
                        //          g
                        //       p     u
                        //         c
                        else
                        {
                            RotateL(parent);
                            RotateR(grandfather);
                            grandfather->_col = RED;
                            cur->_col = BLACK;
                        }

                        //旋转后以grandfather为根的子树每条路径的黑色节点数与没插入之前相同,不影响其他路径,不继续向上处理,退出循环
                        //处理完根是黑的,直接退出;
                        break;
                    }
                }
                //二、parent在grandfather的右边,说明uncle在左边
                else
                {
                    uncle = grandfather->_left;

                    //1、uncle存在,且uncle为红色
                    //parent,uncle变黑色,grandfather变红色,并且继续向上处理
                    //*********   不关心uncle的方向   *******
                    if (uncle && uncle->_col == RED)
                    {
                        parent->_col = uncle->_col = BLACK;
                        grandfather->_col = RED;

                        //需要继续向上处理,直到退出循环;
                        cur = grandfather;
                        parent = cur->_parent;
                    }
                    //2、uncle不存在,或者uncle存在且为黑色              利用旋转的方式处理
                    //**********   uncle左右位置不同,处理方式不同
                    else
                    {
                        //(1)cur是parent的右边,左旋grandfather,
                        // 变色:p变黑,g变红
                        //          g
                        //       u     p
                        //               c
                        if (cur == parent->_right)
                        {
                            RotateL(grandfather);
                            grandfather->_col = RED;
                            parent->_col = BLACK;
                        }

                        //(2)cur是parent的左边,右旋parent,左旋grandfather
                        //变色: c变黑,g变红
                        //          g
                        //       u     p
                        //           c
                        else
                        {
                            RotateR(parent);
                            RotateL(grandfather);
                            grandfather->_col = RED;
                            cur->_col = BLACK;
                        }
                        //旋转后以grandfather为根的子树每条路径的黑色节点数与没插入之前相同,不影响其他路径,不继续向上处理,退出循环;
                        //处理完根是黑的,直接退出;
                        break;
                    }
                }
            }
            //最后让根节点的颜色一定为黑色,不在循环里面单独判断是否为根节点
            _root->_col = BLACK;


            return make_pair(iterator(newnode),true);
        }
        //左旋
        void RotateL(Node* parent)
        {
            Node* subR = parent->_right;
            Node* subRL = subR->_left;

            //处理parent与subRL新关系的链接
            parent->_right = subRL;
            //subRL不为空,其父节点指向parent
            if (subRL)
                subRL->_parent = parent;

            //处理parent与subR新关系的链接
            subR->_left = parent;
            Node* ppnode = parent->_parent;//保存旧parent的父亲节点与subR节点链接
            parent->_parent = subR;

            //处理parent的父亲节点与subR节点链接
            //parent可能是根节点
            if (_root == parent)
            {
                _root = subR;
                subR->_parent = nullptr;
            }
            else
            {
                if (parent == ppnode->_left)
                    ppnode->_left = subR;
                else
                    ppnode->_right = subR;
                subR->_parent = ppnode;
            }
        }
        //右旋
        void RotateR(Node* parent)
        {
            Node* subL = parent->_left;
            Node* subLR = subL->_right;

            parent->_left = subLR;
            if (subLR)
                subLR->_parent = parent;

            subL->_right = parent;
            Node* ppnode = parent->_parent;
            parent->_parent = subL;

            if (parent == _root)
            {
                _root = subL;
                subL->_parent = nullptr;
            }
            else
            {
                if (ppnode->_left == parent)
                {
                    ppnode->_left = subL;
                }
                else
                {
                    ppnode->_right = subL;
                }
                subL->_parent = ppnode;
            }
        }

        //中序遍历
        void Inorder()
        {
            _inorder(_root);
            cout << "end" << endl;
        }
        void _inorder(Node* root)
        {
            if (root == nullptr)
                return;
            _inorder(root->_left);
            cout << root->_kv.first << ":" << root->_kv.second << endl;
            _inorder(root->_right);
        }

        iterator find(const K& key)
        {
            Node* cur = _root;
            KeyofT kot;
            while (cur)
            {
                if (key < kot(cur->_data))
                {
                    cur = cur->_left;
                }
                else if (key > kot(cur->_data))
                {
                    cur = cur->_right;
                }
                else
                    return iterator(cur);
            }
            return iterator(nullptr);
        }

    private:
        Node* _root = nullptr;
    };

}

set封装代码

cpp 复制代码
#pragma once

namespace zwb
{
    template<class K>
    class set
    {
        struct SetKeyofT
        {
            const K operator()(const K& data)
            {
                return data;
            }
        };

    public:

        //类模板里取东西,编译器不知道是类型还是静态成员变量,typename告诉编译器是类型,
        typedef typename RBTree<K, const K, SetKeyofT>::iterator iterator;
        typedef typename RBTree<K, K,SetKeyofT>::const_iterator const_iterator;

        const_iterator begin() const
        {
            return _t.begin();
        }
        const_iterator end()const
        {
            return _t.end();
        }

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

        pair<iterator, bool> insert(const K& data)
        {
            return _t.Insert(data);
        }

        iterator find(const K& key)
        {
            return _t.find(key);
        }


    private:
        RBTree<K, const K, SetKeyofT> _t;
    };

}

map封装代码

cpp 复制代码
namespace zwb
{
    template<class K, class V>
    class map
    {
        //用于取出(data)pair<k,v>中的k值
        struct MapKeyofT
        {
            const K operator()(const pair<K, V>& data)
            {
                return data.first;
            }
        };

    public:
        //迭代器
        
        //类模板里取东西,编译器不知道是类型还是静态成员变量,typename告诉编译器是类型,
        typedef typename RBTree<K, pair<const K, V>, MapKeyofT>::iterator iterator;
        typedef typename RBTree<K, pair<const K, V>, MapKeyofT>::const_iterator const_iterator;

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

        const_iterator begin() const
        {
            return _t.begin();
        }
        const_iterator end() const
        {
            return _t.end();
        }

        //查找
        iterator find(const K& key)
        {
            return _t.find(key);
        }

        pair<iterator,bool> insert(const pair<K,V>& data)
        {
            return _t.Insert(data);
        }

        //重载方括号
        V& operator[](const K& key)
        {
            pair<iterator, bool> ret = insert(make_pair(key, V()));
            return   (*(ret.first)).second;
        }

        

    private:
        RBTree<K, pair<const K, V>, MapKeyofT> _t;
    };

测试代码

cpp 复制代码
 void test_set1()
    {
        set<int> s;
        int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
        for (auto e : a)
        {
            s.insert(e);
        }
        set<int>::iterator it = s.begin();
        while (it != s.end())
        {
            /*if(*it % 2 == 0)
            	*it += 100;*/
            //保证迭代器不能修改,传参时候,传参为const类型

            cout<< *it << " ";
            ++it;
        }
        cout << endl;
    }


 void test_map1()
    {
        map<int, int> m;
        int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
        for (auto e : a)
        {
            m.insert(make_pair(e, e));
        }
        map<int,int>::iterator it = m.begin();
        while (it != m.end())
        {
            /*if(it->first % 2 == 0)
            it->first+= 100;*/
            //保证map的key不可修改,在pair中传参const K

            cout << it->first<<" : " <<it->second << endl;
            ++it;
        }
        cout << endl;
    }

    void test_find()
    {
        map<int, int> m;
        int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
        for (auto e : a)
        {
            m.insert(make_pair(e, e));
        }
        auto f = m.find(3);
        cout << f->first << endl;

        set<int> s;
        for (auto e : a)
        {
            s.insert(e);
        }
        auto sf = s.find(3);
        cout <<*sf << endl;
    }
    
    void test_sub()
    {
       
        map<int, int> m;
        int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
        for (auto e : a)
        {
            m.insert(make_pair(e, e));
        }
        auto f = m.find(15);
        cout << (--f)->first << endl;
        cout << (--f)->first << endl;
        cout << (--f)->first << endl;
        cout << (--f)->first << endl;
        cout << (--f)->first << endl;

        set<int> s;
        for (auto e : a)
        {
            s.insert(e);
        }
        auto sf = s.find(15);
        cout << *(--sf) << endl;
        cout << *(--sf) << endl;
        cout << *(--sf) << endl;
        cout << *(--sf) << endl;
        cout << *(--sf) << endl;
    }



    void test_constiterator() 
    {
        const map<int, int> m;
        map<int, int>::const_iterator it = m.begin();
        while (it != m.end())
        {

            cout << it->first << " : " << it->second << endl;
            ++it;
        }
        cout << endl;
    }


    void test_bracket()
    {
        string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "西瓜", "香蕉", "草莓" };
        map<string, int> countMap;
       
        for (auto& e : arr)
        {
            countMap[e]++;
        }

        for (auto& kv : countMap)
        {
            cout << kv.first << ":" << kv.second << endl;
        }
        cout << endl;
    }


int main() {

    //test_map1();
    //test_set1();
    //test_find();
    //test_sub();
    test_bracket();
    return 0;
}
相关推荐
结衣结衣.26 分钟前
通过指令深入了解Linux
linux·运维·服务器·c语言·开发语言·笔记·学习
Hush_NUIST28 分钟前
C++_sizeof的相关知识点
开发语言·c++·算法·面试
苍狼王unity学院1 小时前
C#列表按照日期进行从大到小排序
算法·unity·c#·游戏引擎·排序算法
Freesial_1 小时前
C# 方法的重载(Overload)
开发语言·c#
Garcia Shan1 小时前
【Dash】使用 Bootstrap Componnet 创建图表
开发语言·python·bootstrap·数据可视化·dash
new60ll1 小时前
决策树基础
算法·决策树·机器学习
檀越剑指大厂2 小时前
【Python系列】使用 `isinstance()` 替代 `type()` 函数
开发语言·python
汤姆和杰瑞在瑞士吃糯米粑粑3 小时前
类和对象(下)C++
开发语言·c++
阿诚学java4 小时前
数据结构与算法-15高级数据结构_树论(堆树)
java·数据结构
王小王-1234 小时前
基于Boost算法的贷款违约预测研究
人工智能·算法·贷款违约预测·基于boosting算法