【C++】set和map的底层结构(AVL树红黑树)

文章目录

  • 一、前言
  • 二、AVL 树
    • 1.AVL树的概念
    • 2.AVL树节点的定义
    • 3.AVL树的插入
    • 4.AVL树的旋转
    • 5.AVL树的验证
    • 6.AVL树的删除、AVL树的性能
  • 三、红黑树
    • 1.红黑树的概念
    • 2.红黑树的性质
    • 3.红黑树节点的定义
    • 4.红黑树结构
    • 5.红黑树的插入操作
    • 6.红黑树的验证
    • 7.红黑树与AVL树比较
  • 四、红黑树模拟实现STL中的map与set
    • 1.红黑树的迭代器
    • 2.改造红黑树
    • 3.map的模拟实现
    • 4.set的模拟实现


一、前言

前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。


二、AVL 树

1.AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)

2.AVL树节点的定义

AVL树节点的定义:

template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft;   // 该节点的左孩子AVLTreeNode<T>* _pRight;  // 该节点的右孩子AVLTreeNode<T>* _pParent; // 该节点的双亲T _data;int _bf;                  // 该节点的平衡因子
};

3.AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子
bool Insert(const T& data)
{// 1. 先按照二叉搜索树的规则将节点插入到AVL树中// ...// 2. 新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否//破坏了AVL树的平衡性 /*pCur插入后,pParent的平衡因子一定需要调整,在插入之前,pParent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:1. 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可2. 如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可此时:pParent的平衡因子可能有三种情况:0,正负1, 正负21. 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,插入成功2. 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,此时以pParent为根的树的高度增加,需要继续向上更新3. 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理*/while (pParent){// 更新双亲的平衡因子if (pCur == pParent->_pLeft)pParent->_bf--;elsepParent->_bf++;// 更新后检测双亲的平衡因子if (0 == pParent->_bf){break;}else if (1 == pParent->_bf || -1 == pParent->_bf){// 插入前双亲的平衡因子是0,插入后双亲的平衡因为为1 或者 -1 ,说明以双亲为根的二叉树// 的高度增加了一层,因此需要继续向上调整pCur = pParent;pParent = pCur->_pParent;}else{// 双亲的平衡因子为正负2,违反了AVL树的平衡性,需要对以pParent// 为根的树进行旋转处理if (2 == pParent->_bf){// ...}else{// ...}}}return true;
}

4.AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。

旋转的目的:

  • 让这颗子树的高度不超过1(降低子树高度);
  • 旋转过程中继续保持它是搜索树;
  • 更新孩子节点的平衡因子;

根据节点插入位置的不同,AVL树的旋转分为四种:

  • 1.新节点插入较高左子树的左侧—左左:右单旋

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

/*上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左
子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子
树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有
右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点
的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:1. 30节点的右孩子可能存在,也可能不存在2. 60可能是根节点,也可能是子树如果是根节点,旋转完成后,要更新根节点如果是子树,可能是某个节点的左子树,也可能是右子树同学们再此处可举一些详细的例子进行画图,考虑各种情况,加深旋转的理解
*/void _RotateR(PNode pParent)
{// pSubL: pParent的左孩子// pSubLR: pParent左孩子的右孩子,PNode pSubL = pParent->_pLeft;PNode pSubLR = pSubL->_pRight;// 旋转完成之后,30的右孩子作为双亲的左孩子pParent->_pLeft = pSubLR;// 如果30的左孩子的右孩子存在,更新亲双亲if (pSubLR)pSubLR->_pParent = pParent;// 60 作为 30的右孩子pSubL->_pRight = pParent;// 因为60可能是棵子树,因此在更新其双亲前必须先保存60的双亲PNode pPParent = pParent->_pParent;// 更新60的双亲pParent->_pParent = pSubL;// 更新30的双亲pSubL->_pParent = pPParent;// 如果60是根节点,根新指向根节点的指针if (NULL == pPParent){_pRoot = pSubL;pSubL->_pParent = NULL;}else{// 如果60是子树,可能是其双亲的左子树,也可能是右子树if (pPParent->_pLeft == pParent)pPParent->_pLeft = pSubL;elsepPParent->_pRight = pSubL;}// 根据调整后的结构更新部分节点的平衡因子pParent->_bf = pSubL->_bf = 0;
}
  • 2.新节点插入较高右子树的右侧—右右:左单旋

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现及情况考虑可参考右单旋。

  • 3.新节点插入较高左子树的右侧—左右:先左单旋再右单旋

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因子的更新

// 旋转之前,60的平衡因子可能是-1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进
// 行调整
void _RotateLR(PNode pParent)
{PNode pSubL = pParent->_pLeft;PNode pSubLR = pSubL->_pRight;// 旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节// 点的平衡因子(pSubLR: pParent左孩子的右孩子)int bf = pSubLR->_bf;// 先对30进行左单旋_RotateL(pParent->_pLeft);// 再对90进行右单旋_RotateR(pParent);if (1 == bf)pSubL->_bf = -1;else if (-1 == bf)pParent->_bf = 1;
}
  • 4.新节点插入较高右子树的左侧—右左:先右单旋再左单旋

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

参考右左双旋。

总结:

假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR

    • 当pSubR的平衡因子为1时,执行左单旋
    • 当pSubR的平衡因子为-1时,执行右左双旋
  2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL

    • 当pSubL的平衡因子为-1是,执行右单旋
    • 当pSubL的平衡因子为1时,执行左右双旋

旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。

5.AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  1. 验证其为二叉搜索树
  • 如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
  1. 验证其为平衡树
  • 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
  • 节点的平衡因子是否计算正确
  1. 验证用例

6.AVL树的删除、AVL树的性能

  • AVL树的删除(了解)

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不错与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。

  • AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。


三、红黑树

1.红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点(每条路径上都包含相同数目的黑色节点)
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点

思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

答:因为组织结构已经确定(构成树的黑色节点一定,红色节点只能在黑色节点之间),无论如何填充红色节点都不会超过全黑节点路径的两倍。

3.红黑树节点的定义

// 节点的颜色
enum Color { RED, BLACK };
// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{RBTreeNode(const ValueType& data = ValueType(),Color color = RED): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _color(color){}RBTreeNode<ValueType>* _pLeft;   // 节点的左孩子RBTreeNode<ValueType>* _pRight;  // 节点的右孩子RBTreeNode<ValueType>* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)ValueType _data;            // 节点的值域Color _color;               // 节点的颜色
};

思考:在节点的定义中,为什么要将节点的默认颜色给成红色的?

答:违背规则3的代价比违背规则4的代价更小(红黑树的性质)。

4.红黑树结构

为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent域指向红黑树的根节点,pLeft域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点,如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5.红黑树的插入操作

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  • 按照二叉搜索的树规则插入新节点
template<class ValueType>
class RBTree
{//……bool Insert(const ValueType& data){PNode& pRoot = GetRoot();if (nullptr == pRoot){pRoot = new Node(data, BLACK);// 根的双亲为头节点pRoot->_pParent = _pHead;_pHead->_pParent = pRoot;}else{// 1. 按照二叉搜索的树方式插入新节点// 2. 检测新节点插入后,红黑树的性质是否造到破坏,//   若满足直接退出,否则对红黑树进行旋转着色处理}// 根节点的颜色可能被修改,将其改回黑色pRoot->_color = BLACK;_pHead->_pLeft = LeftMost();_pHead->_pRight = RightMost();return true;}
private:PNode& GetRoot() { return _pHead->_pParent; }// 获取红黑树中最小节点,即最左侧节点PNode LeftMost();// 获取红黑树中最大节点,即最右侧节点PNode RightMost();
private:PNode _pHead;
};
  • 检测新节点插入后,红黑树的性质是否造到破坏

因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:

  • 约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

  • 情况一: cur为红,p为红,g为黑,u存在且为红

注:下图右边的树为我们的修改目标

总结:解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。

  • 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

总结:p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转;p、g变色–p变黑,g变红

  • 情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑

在这里插入图片描述

总结:p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转;则转换成了情况2

6.红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质
bool IsValidRBTree()
{PNode pRoot = GetRoot();// 空树也是红黑树if (nullptr == pRoot)return true;// 检测根节点是否满足情况if (BLACK != pRoot->_color){cout << "违反红黑树性质二:根节点必须为黑色" << endl;return false;}// 获取任意一条路径中黑色节点的个数size_t blackCount = 0;PNode pCur = pRoot;while (pCur){if (BLACK == pCur->_color)blackCount++;pCur = pCur->_pLeft;}// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数size_t k = 0;return _IsValidRBTree(pRoot, k, blackCount);
}
bool _IsValidRBTree(PNode pRoot, size_t k, const size_t blackCount)
{//走到null之后,判断k和black是否相等if (nullptr == pRoot){if (k != blackCount){cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;return false;}return true;}// 统计黑色节点的个数if (BLACK == pRoot->_color)k++;// 检测当前节点与其双亲是否都为红色PNode pParent = pRoot->_pParent;if (pParent && RED == pParent->_color && RED == pRoot->_color){cout << "违反性质三:没有连在一起的红色节点" << endl;return false;}return _IsValidRBTree(pRoot->_pLeft, k, blackCount) &&_IsValidRBTree(pRoot->_pRight, k, blackCount);
}

7.红黑树与AVL树比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

由于红黑树优秀的性能,它已经被应用于 C++ STL库 – map/set、mutil_map/mutil_set。

四、红黑树模拟实现STL中的map与set

1.红黑树的迭代器

迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代器,需要考虑以前问题:

  • begin() 与 end()

STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?能否给成nullptr呢?答案是行不通的,因为对end()位置的迭代器进行–操作,必须要能找最后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • operator++() 与 operator–()
// 找迭代器的下一个节点,下一个节点肯定比其大
void Increasement()
{//分两种情况讨论:_pNode的右子树存在和不存在// 右子树存在if (_pNode->_pRight){// 右子树中最小的节点,即右子树中最左侧节点_pNode = _pNode->_pRight;while (_pNode->_pLeft)_pNode = _pNode->_pLeft;}else{// 右子树不存在,向上查找,直到_pNode != pParent->rightPNode pParent = _pNode->_pParent;while (pParent->_pRight == _pNode){_pNode = pParent;pParent = _pNode->_pParent;}// 特殊情况:根节点没有右子树if (_pNode->_pRight != pParent)_pNode = pParent;}
}// 获取迭代器指向节点的前一个节点
void Decreasement()
{//分三种情况讨论:_pNode 在head的位置,_pNode 左子树存在,_pNode 左子树不存在// 1. _pNode 在head的位置,--应该将_pNode放在红黑树中最大节点的位置if (_pNode->_pParent->_pParent == _pNode && _pNode->_color == RED)_pNode = _pNode->_pRight;else if (_pNode->_pLeft){// 2. _pNode的左子树存在,在左子树中找最大的节点,即左子树中最右侧节点_pNode = _pNode->_pLeft;while (_pNode->_pRight)_pNode = _pNode->_pRight;}else{// _pNode的左子树不存在,只能向上找PNode pParent = _pNode->_pParent;while (_pNode == pParent->_pLeft){_pNode = pParent;pParent = _pNode->_pParent;}_pNode = pParent;}
}

2.改造红黑树

  • RBTree.h
#pragma onceenum Colour
{RED,BLACK,
};template<class T>
struct RBTreeNode
{T _data;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;Colour _col;RBTreeNode(const T& data):_data(data), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED){}
};template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{typedef RBTreeNode<T> Node;typedef __RBTreeIterator<T, Ref, Ptr> Self;typedef __RBTreeIterator<T, T&, T*> iterator;Node* _node;__RBTreeIterator(Node* node):_node(node){}// 普通迭代器的时候,他是拷贝构造// const迭代器的时候,他是构造,支持用普通迭代器构造const迭代器__RBTreeIterator(const iterator& s):_node(s._node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){if (_node->_right){Node* min = _node->_right;while (min->_left){min = min->_left;}_node = min;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}Self& operator--(){if (_node->_left){Node* max = _node->_left;while (max->_right){max = max->_right;}_node = max;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}};// map->RBTree<K, pair<const K, V>, MapKeyOfT> _t;
// set->RBTree<K, K, SetKeyOfT> _t;
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* left = _root;while (left && left->_left){left = left->_left;}return iterator(left);}iterator end(){return iterator(nullptr);}const_iterator begin() const{Node* left = _root;while (left && left->_left){left = left->_left;}return const_iterator(left);}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(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return make_pair(iterator(cur), false);}}cur = new Node(data);Node* newnode = cur;cur->_col = RED;if (kot(parent->_data) < kot(data)){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}while (parent && parent->_col == RED){Node* grandfater = parent->_parent;if (parent == grandfater->_left){Node* uncle = grandfater->_right;// 情况一  uncle存在且为红if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfater->_col = RED;cur = grandfater;parent = cur->_parent;}else{if (cur == parent->_left){// 情况二RotateR(grandfater);parent->_col = BLACK;grandfater->_col = RED;}else{// 情况三RotateL(parent);RotateR(grandfater);cur->_col = BLACK;grandfater->_col = RED;}break;}}else // (parent == grandfater->_right){Node* uncle = grandfater->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfater->_col = RED;cur = grandfater;parent = cur->_parent;}else{//   g                //      p//         cif (cur == parent->_right){RotateL(grandfater);parent->_col = BLACK;grandfater->_col = RED;}else{//   g                //      p//   cRotateR(parent);RotateL(grandfater);cur->_col = BLACK;grandfater->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(iterator(newnode), true);;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* ppNode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (ppNode == nullptr){_root = subR;_root->_parent = nullptr;}else{if (ppNode->_left == parent){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;}Node* ppNode = parent->_parent;subL->_right = parent;parent->_parent = subL;//if (_root == parent)if (ppNode == nullptr){_root = subL;_root->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}void Inorder(){_Inorder(_root);}void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_Inorder(root->_right);}bool Check(Node* root, int blackNum, const int ref){if (root == nullptr){//cout << blackNum << endl;if (blackNum != ref){cout << "违反规则:本条路径的黑色节点的数量跟最左路径不相等" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "违反规则:出现连续红色节点" << endl;return false;}if (root->_col == BLACK){++blackNum;}return Check(root->_left, blackNum, ref)&& Check(root->_right, blackNum, ref);}bool IsBalance(){if (_root == nullptr){return true;}if (_root->_col != BLACK){return false;}int ref = 0;Node* left = _root;while (left){if (left->_col == BLACK){++ref;}left = left->_left;}return Check(_root, 0, ref);}private:Node* _root = nullptr;
};template<class K>
struct SetKeyOfT
{const K& operator()(const K& key){return key;}
};void testRBTree()
{RBTree<int, int, SetKeyOfT<int>> t;RBTree<int, int, SetKeyOfT<int>>::const_iterator it = t.begin();
}

3.map的模拟实现

  • Map.h
#pragma once#include "RBTree.h"namespace _map
{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;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();}pair<iterator, bool> insert(const pair<const K, V>& kv){return _t.Insert(kv);}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;};void test_map(){int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };map<int, int> m;for (auto e : a){m.insert(make_pair(e, e));}map<int, int>::iterator it = m.begin();while (it != m.end()){//it->first++;it->second++;cout << it->first << ":" << it->second << endl;++it;}cout << endl;map<string, int> countMap;string arr[] = { "ƻ", "", "㽶", "ݮ", "ƻ", "", "ƻ", "ƻ", "", "ƻ", "㽶", "ƻ", "㽶" };for (auto& e : arr){countMap[e]++;}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}}
}

4.set的模拟实现

  • Set.h
#pragma once#include "RBTree.h"namespace _set
{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() const{return _t.begin();}iterator end() const{return _t.end();}// 20:21pair<iterator, bool> insert(const K& key){pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);return pair<iterator, bool>(ret.first, ret.second);}private:RBTree<K, K, SetKeyOfT> _t;};void test_set(){int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };set<int> s;for (auto e : a){s.insert(e);}set<int>::iterator it = s.begin();while (it != s.end()){//*it += 10;cout << *it << " ";++it;}cout << endl;for (auto e : s){cout << e << " ";}cout << endl;}
}
  • Test.cpp
#include <iostream>
#include <set>
#include <map>
#include <string>
using namespace std;#include "RBTree.h"#include "Map.h"
#include "Set.h"int main()
{_map::test_map();_set::test_set();return 0;
}

🌹🌹 map和set的底层原理 的知识大概就讲到这里啦,博主后续会继续更新更多C++ 和 Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

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

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

相关文章

ResizeObserver观察元素宽度的变化

ResizeObserver观察元素宽度的变化 ResizeObserver观察元素宽度的变化 ResizeObserver观察元素宽度的变化 ResizeObserver 构造函数创建一个新的 ResizeObserver 对象&#xff0c;它可以用于监听 Element 内容盒或边框盒或者 SVGElement 边界尺寸的大小。查看详细说明 案例 &l…

小白也能看懂的国内外 AI 芯片概述

随着越来越多的企业将人工智能应用于其产品&#xff0c;AI芯片需求快速增长&#xff0c;市场规模增长显著。因此&#xff0c;本文主要针对目前市场上的AI芯片厂商及其产品进行简要概述。 简介 AI芯片也被称为AI加速器或计算卡&#xff0c;从广义上讲只要能够运行人工智能算法…

SpringCloud实用篇02

SpringCloud实用篇02 0.学习目标 1.Nacos配置管理 Nacos除了可以做注册中心&#xff0c;同样可以做配置管理来使用。 1.1.统一配置管理 当微服务部署的实例越来越多&#xff0c;达到数十、数百时&#xff0c;逐个修改微服务配置就会让人抓狂&#xff0c;而且很容易出错。我…

8Base集团通过SmokeLoader部署新的Phobos勒索软件变种

最近&#xff0c;8Base集团的威胁行为者通过Phobos勒索软件的变种展开了一系列金融动机的攻击。这一发现来自于思科Talos的研究结果&#xff0c;他们记录了网络犯罪分子活动的增加。 安全研究员Guilherme Venere在周五发表的详尽的两部分分析中表示&#xff1a;“该组织的大多…

html2canvas快速使用

一、概述 html2canvas是一个HTML渲染器&#xff0c;是一个脚本&#xff0c;它允许你直接在用户浏览器截取页面或部分网页的“屏幕截屏”。底层是基于DOM的&#xff0c;根据页面上可用的信息构建屏幕截图&#xff0c;它没有制作实际的屏幕截图&#xff0c;因此生成的图片并不一定…

速锐得解码匹配驾培驾考吉利几何E萤火虫数据应用智能评判系统

随着国内新能源车的不断发展和渗透&#xff0c;在驾培驾考领域通过新能源车进入到驾驶员培训领域的车型越来越多&#xff0c;这里边包括了特斯拉、宝马、通用、沃尔沃、岚图、江淮、蔚来、比亚迪、吉利、奇瑞、大众等多家车企的车型。 之前我们做过像奇瑞艾瑞泽、江淮IEV7、大…

电力工作记录仪、智能安全帽、智能布控球助力智能电网建设

电力行业的建设和发展是国家经济发展的重要支撑&#xff0c;而智能电网作为电力系统的重要组成部分&#xff0c;它的安全高效运行关乎到整个电力系统乃至民生的稳定和安全。为了加快国家经济的发展以及满足人们对电力的需求和用电可靠性的要求&#xff0c;国家早在十二规划中就…

leetcode:914. 卡牌分组(python3解法)

难度&#xff1a;简单 给定一副牌&#xff0c;每张牌上都写着一个整数。 此时&#xff0c;你需要选定一个数字 X&#xff0c;使我们可以将整副牌按下述规则分成 1 组或更多组&#xff1a; 每组都有 X 张牌。组内所有的牌上都写着相同的整数。 仅当你可选的 X > 2 时返回 tru…

环境配置|GitHub——如何在github上搭建自己写的网站

下面简单地总结了从本地的网页文件到在github服务器上展示出来即可以通过网络端打开的过程&#xff1a; &#xff08;以下可能会出现一些难点&#xff0c;照着做就可以了&#xff0c;由于笔者是小白&#xff0c;也不清楚具体原理是什么&#xff0c;希望有一天成为大神的时候能轻…

听GPT 讲Rust源代码--src/librustdoc(2)

题图来自 Chromium项目将支持Rust编程语言[1] File: rust/src/librustdoc/html/render/search_index.rs 在Rust源代码中&#xff0c;rust/src/librustdoc/html/render/search_index.rs文件的作用是生成搜索索引&#xff0c;用于在Rust文档页面上进行关键字搜索。该文件实现了一…

战备器材管理系统-部队物资仓库管理系统

一、项目背景 传统的战备物资管理&#xff0c;一般依赖于一个非自动化的、以纸张文件为基础的系统来记录、追踪进出的货物&#xff0c;完全由人工实施仓库内部的管理&#xff0c;因此仓库管理的效率极其低下。对此&#xff0c;我们运用无线射频技术(RFID)的仓库智能管理系统&am…

DSP介绍及CCS

文章目录 CCS版本编译器CCS使用注意严禁中文 CCS的基本操作新建工程导入现有工程调整字体的大小工程界面恢复标签的使用 仿真盒小虫子进入在线Debug 芯片TMS320F28355基本介绍特性 DSP中特殊指令dsp指令中的EALLOW EDIS CCS TI官网 版本 CCS版本&#xff1a; CCS8.3.1.0004_…

〖大前端 - 基础入门三大核心之JS篇㊵〗- DOM事件监听及onxxx的使用

说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费&#xff0c;如需要项目实战或者是体系化资源&#xff0c;文末名片加V&#xff01;作者&#xff1a;不渴望力量的哈士奇(哈哥)&#xff0c;十余年工作经验, 从事过全栈研发、产品经理等工作&#xf…

多目标应用:基于非支配排序的鲸鱼优化算法NSWOA求解微电网多目标优化调度(MATLAB代码)

一、微网系统运行优化模型 微电网优化模型介绍&#xff1a; 微电网多目标优化调度模型简介_IT猿手的博客-CSDN博客 二、基于非支配排序的鲸鱼优化算法NSWOA 基于非支配排序的鲸鱼优化算法NSWOA简介&#xff1a; 三、基于非支配排序的鲸鱼优化算法NSWOA求解微电网多目标优化…

从android.graphics.Path中取出Point点,Kotlin

从android.graphics.Path中取出Point点&#xff0c;Kotlin /*** 从一条Path中获取多少个Point点*/private fun getPoints(path: Path, pointCount: Int): Array<FloatPoint?> {val points arrayOfNulls<FloatPoint>(pointCount)val pm PathMeasure(path, false)…

MySQL索引,你真的学会了?索引底层原理是什么?索引什么时候失效,你知道吗?

目录 1、什么是索引 2、索引分类 3、索引的基本操作 3.1、主键索引 3.2、单列索引 3.3、唯一索引 3.4、复合索引 4、索引的底层原理 为什么使用BTree而不是B-Tree? 如果数据量特别大的情况下&#xff0c;BTree会不会深度太深影响查询效率&#xff1f; 5、聚簇索引和…

OpenGL_Learn15(投光物)

1. 平行光 cube.vs******************#version 330 core layout (location 0) in vec3 aPos; layout (location 1 ) in vec3 aNormal; layout (location2) in vec2 aTexCoords;out vec3 FragPos; out vec3 Normal; out vec2 TexCoords;uniform mat4 model; uniform mat4 view…

车牌识别 支持12种中文车牌类型 车牌数据集下载

开源代码 如果觉得有用&#xff0c;不妨给个Star⭐️&#x1f31f;支持一下吧~ 谢谢&#xff01; Acknowledgments & Contact 1.WeChat ID: cbp931126 2.QQ Group&#xff1a;517671804 加微信(备注&#xff1a;PlateAlgorithm),进讨论群可以获得10G大小的车牌检测和识…

DPAFNet:一种用于多模式脑肿瘤分割的残差双路径注意力融合卷积神经网络

DPAFNet: A Residual Dual-Path Attention-Fusion Convolutional Neural Network for Multimodal Brain Tumor Segmentation DPAFNet&#xff1a;一种用于多模式脑肿瘤分割的残差双路径注意力融合卷积神经网络背景贡献实验方法ulti-scale context feature extraction block&…

【Spring】之IoC与对象存取

未来的几周时间&#xff0c;大概率我会更新一下Spring家族的一些简单知识。而什么是Spring家族&#xff0c;好多同学还不是很清楚&#xff0c;我先来简单介绍一下吧&#xff1a; 所谓Spring家族&#xff0c;它其实就是一个框架&#xff0c;是基于Servlet再次进行封装的内容。为…