文章目录

  • 4. 底层结构
    • 4.1 AVL 树
      • 4.1.1 AVL树的概念
      • 4.1.2AVL树节点的定义
      • 4.1.3 AVL树的插入
      • 4.1.4 AVL树的旋转
        • 1. 新节点插入较高左子树的左侧---左左:右单旋
        • 3. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋
        • 4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋
      • 4.1.5 AVL树的验证
      • 4.1.6AVL树的性能
    • 4.2 红黑树
      • 4.2.1 红黑树的概念
      • 4.2.2 红黑树的性质
      • 4.2.4 红黑树结构
      • 4.2.3 红黑树节点的定义
      • 4.2.5 红黑树的插入操作
      • 4.2.6 红黑树的验证
      • 4.2.8 红黑树与AVL树的比较
    • 4.3 红黑树模拟实现STL中的map与set
      • 4.3.1 红黑树的迭代器
        • begin\end
        • operator++\--
      • 4.3.2改造红黑树
      • 4.3.3 map的模拟实现
      • 4.3.4 set的模拟实现

4. 底层结构

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

4.1 AVL 树

高度平衡二叉搜索树,是以人名命名的

4.1.1 AVL树的概念

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

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

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

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

平衡因子需要三叉链
但是旋转的时候也会变复杂

4.1.2AVL树节点的定义

t

emplate<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;pair<K, V> _kv;
int _bf;  // balance factorAVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _bf(0)
{}
};

4.1.3 AVL树的插入

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

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

更新平衡因子的规则
1.新增在右,parent->bf++;新增在左,parent->bf–
 
2.更新后,parent->bf == 1 or -1,说明parent插入前的平衡因子是0,说明左右子树高度相等,插入后有一边高,parent高度变了,需要继续往上更新
 
3.更新后,parent->bf == 0,说明parent插入前的平衡因子是1或-1,说明左右子树一边高一边低,插入后两边一样高,插入填上了矮的那边,parent所在子树高度不变,不需要继续往上更新
 
4.更新后,parent->bf == 2 or -2,说明parent插入前的平衡因子是1或-1,已经到达平衡临界值,插入后变成2或-2,打破平衡,parent所在子树需要旋转处理
 
5.更新后,parent->>2 or <-2的值,不可能,如果存在,说明插入前的就不是AVL数,需要去检查之前操作的问题

//插入
cur = new Node(kv);
//链接
if (parent->_kv.first < kv.first)
{//插入的值大就插入右边
parent->_right = cur;
}
else
{//插入的值小就插入左边
parent->_left = cur;
}
//三叉链还要考虑parent
cur->_parent = parent;// 控制平衡
// 1、更新平衡因子
while (parent)
{if (cur == parent->_right)
{parent->_bf++;
}
else
{parent->_bf--;
}if (parent->_bf == 0)
{break;
}
else if (abs(parent->_bf) == 1)
{//往上更新
parent = parent->_parent;
cur = cur->_parent;
}
else if (abs(parent->_bf) == 2)
{// 说明parent所在子树已经不平衡了,需要旋转处理
if (parent->_bf == 2 && cur->_bf == 1)
{RotateL(parent);
}
else if ((parent->_bf == -2 && cur->_bf == -1))
{RotateR(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{RotateLR(parent);
}break;
}
else
{assert(false);
}
}return true;
}

4.1.4 AVL树的旋转

原则:

  1. 旋转成平衡树
  2. 保持搜索树的规则
 else if (abs(parent->_bf) == 2)
{// 说明parent所在子树已经不平衡了,需要旋转处理
if (parent->_bf == 2 && cur->_bf == 1)
{RotateL(parent);
}
else if ((parent->_bf == -2 && cur->_bf == -1))
{RotateR(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{RotateLR(parent);
}else if (parent->_bf == 2 && cur->_bf == -1)
{RotateRL(parent);
}break;
}

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种

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

a、b、c是h >= 0的平衡树
左边高,往右边旋转
只有parent和subL的平衡因子受影响——全变为0

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)
{_root = subL;
subL->_parent = nullptr;
}
else
{if (ppNode->_left == parent)
{ppNode->_left = subL;
}
else
{ppNode->_right = subL;
}subL->_parent = ppNode;
}subL->_bf = parent->_bf = 0;
}
  1. 新节点插入较高右子树的右侧—右右:左单旋

parent是整棵树的根
parent是子树的根
要动六个指针
只有parent和subR的平衡因子受影响——全变为0

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 (_root == parent)
{_root = subR;
subR->_parent = nullptr;
}
else
{if (ppNode->_left == parent)
{ppNode->_left = subR;
}
else
{ppNode->_right = subR;
}subR->_parent = ppNode;
}subR->_bf = parent->_bf = 0;
}

单旋(1、2):a、b、c是高度为h的AVL子树,h>=0; 旋转的价值和意义:

  • 平衡 降高度(高度恢复到插入之前的样子)
  • 新节点插入较高左子树的右侧—左右:先左单旋再右单旋

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

void RotateLR(Node* parent)
{Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;RotateL(parent->_left);
RotateR(parent);subLR->_bf = 0;
if (bf == 1)
{parent->_bf = 0;
subL->_bf = -1;
}
else if (bf == -1)
{// 错的
/*parent->_bf = 0;
subL->_bf = 1;*/parent->_bf = 1;
subL->_bf = 0;
}
else if (bf == 0)
{parent->_bf = 0;
subL->_bf = 0;
}
else
{assert(false);
}
}

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

参考右左双旋。

void RotateRL(Node* parent)
{Node* subR = parent->_right;
Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);
RotateL(parent);subRL->_bf = 0;
if (bf == 1)
{subR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{subR->_bf = 1;
parent->_bf = 0;
}
else if (bf == 0)
{parent->_bf = 0;
subR->_bf = 0;
}
else
{assert(false);
}
}

双旋(3、4):a、b、c、d高度位h或者h-1的AVL树 三种插入情况:

  1. b插入新增,引发双旋
  2. c插入新增,引发双旋
  3. a、b 、c、d是空树,60是新增(用图片举例),引发双旋

双旋过程不变,平衡因子的更新需要分别处理

总结: 假如以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为根的子树个高度降低,已经平衡,不需要再向上更新

4.1.5 AVL树的验证

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

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

public: bool IsBalance() { return _IsBalance(_root); } private: bool
_IsBalance(Node* root) { // 空树也是AVL树

if (root == nullptr) { return true; }

int leftHT = Height(root->_left); int rightHT = Height(root->_right);
int diff = rightHT - leftHT;//差值

if (diff != root->_bf) { cout << root->_kv.first << “平衡因子异常” << endl;
return false; }

return abs(diff) < 2 && _IsBalance(root->_left) &&
_IsBalance(root->_right);//自己判断完之后再去判断左子树和右子树 }

//求高度 int Height(Node* root) { if (root == nullptr) return 0;

return max(Height(root->_left), Height(root->_right)) + 1; }

  1. 验证用例
    常规场景1
    {16, 3, 7, 11, 9, 26, 18, 14, 15}
    特殊场景2
    {4, 2, 6, 1, 3, 5, 15, 7, 16, 14}
void TestAVLTree1()
{int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };  // 测试双旋平衡因子调节
//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
AVLTree<int, int> t1;
for (auto e : a)
{t1.Insert(make_pair(e, e));
}t1.InOrder();
cout << "IsBalance:" << t1.IsBalance() << endl;
}//给随机值测试用例
void TestAVLTree2()
{size_t N = 10000;
srand(time(0));
AVLTree<int, int> t1;
for (size_t i = 0; i < N; ++i)
{int x = rand();
t1.Insert(make_pair(x, i));
/*bool ret = t1.IsBalance();
if (ret == false)
{
int u = 1;
}
else
{
cout << "Insert:" << x << " IsBalance:" <<ret<< endl;
}*/
}
cout << "IsBalance:" << t1.IsBalance() << endl;
}

4.1.6AVL树的性能

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

4.2 红黑树

AVL树:要求左右高度差不超过1(严格平衡)
红黑树:最长路径不超过最短路径的2倍(不严格——近似平衡)
效果:相对而言,插入同样数据,AVL树旋转更多,红黑树旋转更少

4.2.1 红黑树的概念

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

4.2.2 红黑树的性质

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

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

极限最短:全黑
极限最长:一黑一红……

路径不是算到叶子,完整的路径是要算到空结点

4.2.4 红黑树结构

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

4.2.3 红黑树节点的定义

// 节点的颜色
enum Colour
{RED,
BLACK
};// 红黑树节点的定义
template<class K, class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;// 节点的左孩子
RBTreeNode<K, V>* _right;// 节点的右孩子
RBTreeNode<K, V>* _parent;// 节点的双亲(红黑树需要旋转,为了实现简单给
出该字段)pair<K, V> _kv;// 节点的值域
Colour _col;// 节点的颜色RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
{}
};

4.2.5 红黑树的插入操作

新结点插入,是什么颜色?
默认给红色。——违反规则3比违反规则4好一点
新插入的结点的父亲是红的话一定要将其变黑

处理规则:
1.变色
2.旋转

红黑树的关键是叔叔!
u存在且为红,变色继续往上处理
u不存在或存在且为黑,旋转+变色(单旋+双旋)

  1. 按照二叉搜索的树规则插入新节点
  2. 检测新节点插入后,红黑树的性质是否造到破坏

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

情况一: cur为红,p为红,g为黑,u存在且为红
cur和p均为红,违反了性质三,此处能否将p直接改为黑?
解决方式:将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

bool Insert(const pair<k,v>& kv)
{if (_root == nullptr)
{_root = new Node(kv);
_root->_col = BLACK;//根结点为黑色
return true;
}Node* parent = nullptr;
Node* cur = _root;
while (cur)
{if (cur->_kv.first < kv.first)
{parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > kv.first)
{parent = cur;
cur = cur->_left;
}
else
{return false;
}
}cur = new Node(kv);
cur->_col = RED;//新插入的结点默认为红色if (parent->_kv.first < kv.first)
{parent->_right = cur;
}
else
{parent->_left = cur;
}cur->_parent = parent;//parent存在且颜色为红色,继续处理while (parent && parent->_col == RED)
{Node* grandfater = parent->_parent;
assert(grandfater);
assert(grandfater->_col == BLACK);// 关键看叔叔
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;
}// 情况二+三:uncle不存在 + 存在且为黑
else
{// 情况二:右单旋+变色
//     g
//   p   u
// c
if (cur == parent->_left)
{RotateR(grandfater);
parent->_col = BLACK;
grandfater->_col = RED;
}
else
{// 情况三:左右单旋+变色
//     g
//   p   u
//     c
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
//   u   p
//         c
if (cur == parent->_right)
{RotateL(grandfater);
parent->_col = BLACK;
grandfater->_col = RED;
}
else
{// 情况三:右左单旋+变色
//     g
//   u   p
//     c
RotateR(parent);
RotateL(grandfater);
cur->_col = BLACK;
grandfater->_col = RED;
}break;
}
}}_root->_col = BLACK;
return true;
}

4.2.6 红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质
public:bool IsBalance()
{if (_root == nullptr)
{return true;
}if (_root->_col == RED)
{cout << "根节点不是黑色" << endl;
return false;
}// 黑色节点数量基准值
int benchmark = 0;
/*Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
++benchmark;cur = cur->_left;
}*/return PrevCheck(_root, 0, benchmark);
}private:
bool PrevCheck(Node* root, int blackNum, int& benchmark)
{if (root == nullptr)
{//cout << blackNum << endl;
//return;
if (benchmark == 0)
{benchmark = blackNum;
return true;
}if (blackNum != benchmark)
{cout << "某条黑色节点的数量不相等" << endl;
return false;
}
else
{return true;
}
}if (root->_col == BLACK)
{++blackNum;
}if (root->_col == RED && root->_parent->_col == RED)
{cout << "存在连续的红色节点" << endl;
return false;
}return PrevCheck(root->_left, blackNum, benchmark)
&& PrevCheck(root->_right, blackNum, benchmark);
}

递归中,blackNum记录根结点——当前结点路径上黑色结点数量
前序递归遍历即可

4.2.8 红黑树与AVL树的比较

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

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

4.3.1 红黑树的迭代器

T是一个泛型,没有具体类型
增加一个参数 KeyOfT 是一个仿函数——方便比较大小
作用:将value中的key提取出来

begin\end

  • begin返回中序第一个
  • end返回最后一个结点的下一个
template<class K, class T, class KeyOfT>
struct RBTree
{typedef RBTreeNode<T> Node;
public:
typedef __RBTreeIterator<T, T&, T*> iterator;iterator begin()
{Node* left = _root;
while (left && left->_left)
{left = left->_left;
}return iterator(left);
}iterator end()
{return iterator(nullptr);
}.......}

operator+±-

++
中序:左子树 根 右子树
右子树不为空,++就是找右子树中序第一个(最左结点)
右子树为空,++找孩子不是父亲右的那个祖先

--
中序反过来:右子树 根 左子树
左子树不为空,–访问左子树中的最右结点
左子树为空,–找孩子不是父亲左的那个祖先

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)
{}Ref operator*()
{return _node->_data;
}Ptr operator->()
{return &_node->_data;
}bool operator!=(const Self& s) const
{return _node != s._node;
}bool operator==(const Self& s) const
{return _node == s._node;
}Self& operator++()
{if (_node->_right)
{// 下一个就是右子树的最左节点
Node* left = _node->_right;
while (left->_left)
{left = left->_left;
}_node = left;
}
else
{// 找祖先里面孩子不是祖先的右的那个
Node* parent = _node->_parent;
Node* cur = _node;
while (parent && cur == parent->_right)
{cur = cur->_parent;
parent = parent->_parent;
}_node = parent;
}return *this;
}Self& operator--()
{if (_node->_left)
{// 下一个是左子树的最右节点
Node* right = _node->_left;
while (right->_right)
{right = right->_right;
}_node = right;
}
else
{// 孩子不是父亲的左的那个祖先
Node* parent = _node->_parent;
Node* cur = _node;
while (parent && cur == parent->_left)
{cur = cur->_parent;
parent = parent->_parent;
}_node = parent;
}return *this;
}
};

4.3.2改造红黑树

#pragma onceenum Colour
{RED,BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data){}
};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){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}Self& operator++(){if (_node->_right){// 下一个就是右子树的最左节点Node* left = _node->_right;while (left->_left){left = left->_left;}_node = left;}else{// 找祖先里面孩子不是祖先的右的那个Node* parent = _node->_parent;Node* cur = _node;while (parent && cur == parent->_right){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}Self& operator--(){if (_node->_left){// 下一个是左子树的最右节点Node* right = _node->_left;while (right->_right){right = right->_right;}_node = right;}else{// 孩子不是父亲的左的那个祖先Node* parent = _node->_parent;Node* cur = _node;while (parent && cur == parent->_left){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}
};template<class K, class T, class KeyOfT>
struct RBTree
{typedef RBTreeNode<T> Node;
public:typedef __RBTreeIterator<T, T&, T*> iterator;iterator begin(){Node* left = _root;while (left && left->_left){left = left->_left;}return iterator(left);}iterator end(){return iterator(nullptr);}pair<iterator, bool> Insert(const T& data){KeyOfT kot;if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(iterator(_root), true);}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;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfater = parent->_parent;assert(grandfater);assert(grandfater->_col == BLACK);// 关键看叔叔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;}// 情况二+三:uncle不存在 + 存在且为黑else{// 情况二:右单旋+变色//     g //   p   u// cif (cur == parent->_left){RotateR(grandfater);parent->_col = BLACK;grandfater->_col = RED;}else{// 情况三:左右单旋+变色//     g //   p   u//     cRotateL(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 //   u   p//         cif (cur == parent->_right){RotateL(grandfater);parent->_col = BLACK;grandfater->_col = RED;}else{// 情况三:右左单旋+变色//     g //   u   p//     cRotateR(parent);RotateL(grandfater);cur->_col = BLACK;grandfater->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(iterator(newnode), true);}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){if (_root == nullptr){return true;}if (_root->_col == RED){cout << "根节点不是黑色" << endl;return false;}// 黑色节点数量基准值int benchmark = 0;return PrevCheck(_root, 0, benchmark);}private:bool PrevCheck(Node* root, int blackNum, int& benchmark){if (root == nullptr){//cout << blackNum << endl;//return;if (benchmark == 0){benchmark = blackNum;return true;}if (blackNum != benchmark){cout << "某条黑色节点的数量不相等" << endl;return false;}else{return true;}}if (root->_col == BLACK){++blackNum;}if (root->_col == RED && root->_parent->_col == RED){cout << "存在连续的红色节点" << endl;return false;}return PrevCheck(root->_left, blackNum, benchmark)&& PrevCheck(root->_right, blackNum, benchmark);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}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 (_root == parent){_root = subR;subR->_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){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}private:Node* _root = nullptr;
};

4.3.3 map的模拟实现

#pragma once#include "RBTree.h"namespace haha
{template<class K, class V>
class map
{//作用:将value中的key提取出来struct MapKeyOfT
{const K& operator()(const pair<K, V>& kv)
{return kv.first;
}
};
public:
typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;iterator begin()
{return _t.begin();
}iterator end()
{return _t.end();
}pair<iterator, bool> insert(const pair<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<K, V>, MapKeyOfT> _t;
};void test_map()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;
for (auto& str : arr)
{// 1、str不在countMap中,插入pair(str, int()),然后在对返回次数++
// 2、str在countMap中,返回value(次数)的引用,次数++;
countMap[str]++;
}map<string, int>::iterator it = countMap.begin();
while (it != countMap.end())
{cout << it->first << ":" << it->second << endl;
++it;
}for (auto& kv : countMap)
{cout << kv.first << ":" << kv.second << endl;
}
}
}

4.3.4 set的模拟实现

#pragma once#include "RBTree.h"namespace haha
{template<class K>
class set
{struct SetKeyOfT
{const K& operator()(const K& key)
{return key;
}
};
public:
typedef typename RBTree<K, K, SetKeyOfT>::iterator 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_set()
{set<int> s;set<int>::iterator it = s.begin();
while (it != s.end())
{cout << *it << " ";
++it;
}
cout << endl;s.insert(3);
s.insert(2);
s.insert(1);
s.insert(5);
s.insert(3);
s.insert(6);
s.insert(4);
s.insert(9);
s.insert(7);it = s.begin();
while (it != s.end())
{cout << *it << " ";
++it;
}
cout << endl;
}
}

map、set(底层结构)——C++相关推荐

  1. Go map的底层原理(存储、扩容)

    Go map的底层原理 map的实现原理 map的底层结构 map的扩容机制 map的实现原理 数组+链表.拉链法 map的底层结构 hmap 哈希表 hmap是Go map的底层实现,每个hmap内 ...

  2. golang map嵌套struct 结构体字段 不能直接修改 解决方法

    目录 错误信息 错误原因 解决方法 错误信息 Reports assignments directly to a struct field of a map 错误原因 结构体作为map的元素时,不能够 ...

  3. HP-lefthand底层结构具体解释及存储灾难数据恢复

    HP-lefthand底层结构具体解释及存储灾难数据恢复 一.HP-lefthand的特点 HP-lefhand是一款很不错的SAN存储,使用iscsi协议为client分配空间. 它支持RAID5. ...

  4. Golang map的底层实现

    转自https://blog.csdn.net/i6448038/article/details/82057424并修改 map是Go语言中基础的数据结构,在日常的使用中经常被用到.但是它底层是如何实 ...

  5. Go语言的string(底层结构+常用方法)

    字符串 Go语言中的字符串是通过UTF-8编码,字符串的值为双引号(")中的内容,可以在Go语言的源码中直接添加非ASCII码字符 . 字符串底层结构是一个起始地址和长度(字节个数) 字符串 ...

  6. Java基础笔记(2)——HashMap的源码,实现原理,底层结构是怎么样的

    Java基础笔记(2)--HashMap的源码,实现原理,底层结构是怎么样的 HashMap的源码,实现原理,底层结构 1.HashMap: HashMap是基于哈希表的 Map 接口的实现.此实现提 ...

  7. 聊聊Java系列-集合之HashMap底层结构原理

    前言           由于HashMap在我们的工作和面试中会经常遇到,所以搞懂HashMap的底层结构原理就显得十分有必要了.在JDK1.8之前,HashMap的底层采用的数据结构是数组+链表, ...

  8. 小码哥iOS学习笔记第八天: block的底层结构

    一.最简单的block 1.最简单的block结构 ^{NSLog(@"this is a block");NSLog(@"this is a block"); ...

  9. map可以用结构体作为健值吗?

    map可以用结构体作为健值吗 前言 map可以用结构体作为健值 前言 在使用map时,有时候我们需要自定义键值,才能符合程序的需要. 比如我们需要使用自定义的结构体来作为map的键值: struct ...

最新文章

  1. 总在说SpringBoot内置了tomcat启动,那它的原理你说的清楚吗?
  2. 数据库本地服务器为空,本地搭建的服务器访问不到数据库数据
  3. 爱是相互的,这样才是平衡
  4. Flutter混合开发:Android中如何启动Flutter
  5. 创业与老子的顺其自然
  6. oracle基础入门(二)
  7. thinkphp5的Illegal string offset 'id'错误
  8. 【图像处理基础】基于matlab GUI图片浏览器【含Matlab源码 1015期】
  9. 如何设置路由器的中继模式-机器人局域网组网攻略
  10. matlab生成流程图,matlab做流程图
  11. WebSphere 异常问题记录
  12. 查询历史使用过的命令并使用(history)
  13. 关于轩微电子ADS1256+stm32f103开发板的一点使用小tip
  14. 免费CMS插件文章采集伪原创发布插件
  15. Python装逼神器,5 行 Python 代码 实现一键批量扣图!
  16. Qt中textEdit文本编辑区设置滚动条自动向下滑落
  17. 数据集分类不平衡的影响与处理
  18. Spring事务抛出Exception异常不回滚
  19. 鬼影3启动的技术细节
  20. 杨建允:新电商助力企业实现新品牌营销运营发展进阶

热门文章

  1. 非常量引用的初始值必须为左值
  2. 【layui】图片查看器
  3. java一条System.out语句打印多个变量
  4. 51la流量获取链接
  5. “我是集美貌与才华于一身的PAPI酱”,她为什么可以融到1200万
  6. 基金持仓数据分析,满仓干还是等风来?
  7. 爱尔眼科跌超11%,葛兰管理的中欧医疗健康混合基金产品重仓股
  8. MySQL无法启动报 Error: could not open single-table tablespace file ./mysql/innodb_table_sta
  9. k8s之PV以及PVC
  10. c语言怎样把除法转为乘法,怎样代替除法指令