• C++语法——map与set的封装原理


    目录

    一.数据类型封装

    (一).封装方式

    (二).封装后如何取key比较

     二.迭代器封装

    (一).底层迭代器(红黑树中)

    ①迭代器++

    ②迭代器-- 

    (二).begin&end&const


    一.数据类型封装

    (一).封装方式

    我们知道,map与set所使用的都是红黑树,但是map是key&value模型,set是key模型。

    map需要传两个类型而set只用传一个类型,这在底层红黑树中该怎么兼容呢?

    透过SGI版stl_tree.h我们可以看到,红黑树有两个模板参数分别表示key和value。重点来了:

    对于map而言需要传入key和pair分别给红黑树的key和value。

    set需要传key和key给红黑树的key和value。

    再看map与set源码(进行部分截取):

     如果自制的话,大致如此:

    1. template<class K, class V>
    2. class Map
    3. {
    4. typedef rb_tree, MapOfV>> tree;
    5. public:
    6. //...
    7. private:
    8. tree _t;
    9. };
    1. template<class K>
    2. class Set
    3. {
    4. typedef rb_tree> tree;
    5. public:
    6. //...
    7. private:
    8. tree _t;
    9. };

    (二).封装后如何取key比较

    但是这种封装有一个问题,就是在底层红黑树中,是直接拿value进行比较,那这该怎么办呢?

    仿函数取value中key值来解决。

    对于map而言,传仿函数给红黑树,调用仿函数取value(实际是pair)中的key值

    对于set而言,key和value中的值实际上都是key,也传仿函数取即可。

    因此,对于上述代码我们看到map和set分别传入了仿函数MapOfV和SetOfV。

    仿函数均在上层中(map/set)实现,传入底层红黑树。

    代码实现如下(简易):

    1. template<class K, class V>
    2. //传入分别是key和pair
    3. //typedef rb_tree, MapOfV>> tree;
    4. class MapOfV
    5. {
    6. public:
    7. K operator()(V kv) const
    8. {
    9. return kv.first;
    10. }
    11. };
    1. template<class K>
    2. //typedef rb_tree> tree;
    3. class SetOfV
    4. {
    5. public:
    6. K operator()(K k) const
    7. {
    8. return k;
    9. }
    10. };

     二.迭代器封装

    因为map和set底层都是红黑树,因此迭代器实现在红黑树中(底层),而map和set只是调用红黑树的迭代器

    (一).底层迭代器(红黑树中)

    因为红黑树也是由一个个节点组成,这就很容易想到list的迭代器。list迭代器是用类封装,迭代器内部定义一个指针指向节点,对迭代器的操作底层是对迭代器内部指针的操作。

    红黑树的迭代器与之类似,也是使用一个类来表示迭代器,内部有一个指向树节点的指针。只要了解过list迭代器都能想出来,不了解可以看看这篇文章哦:为什么List的迭代器是类模板?

    对于解引用*和箭头->的封装与list相同,直接转化成对节点指针的操作。

    关键在于++和--。 

    ①迭代器++

    红黑树也是二叉树,我们就直接以二叉树为例:


     这里要分两种情况: 

     第一种:节点有右子树,此时要向下走。

    以1号节点为例,因为有右子树,++后就是6号节点?——错!

    二叉树迭代是前序遍历的方式,++后应该是5号节点,即右子树的最左节点!

    第二种:节点没有右子树,此时要向上走。

    对比3号和5号节点,都是叶子节点,但是3号节点++后是1号节点,5号++后是6号节点。

    得出结论:如果当前节点是父节点的左子树,那++后就是父节点如果是右子树,那么需要向上找直到当前节点是父节点左子树,此时父节点是++后节点。

    如果是节点7那么++会一直判断到根节点4,因此,迭代器end()可以设置为root的父节点(null)。

    原理很简单,如果是右子节点那说明对于父节点而言你就是++后会找到的节点,但是对于自己而言++后就不可能是父节点。

    代码如下:

    1. Self operator++()
    2. {
    3. goBack();
    4. return *this;
    5. }
    6. void goBack()
    7. {
    8. if (_node->right)//如果右子节点不为空
    9. {
    10. Node* right = _node->right;
    11. while (right->left)//直到找到右子树最左节点
    12. {
    13. right = right->left;
    14. }
    15. _node = right;
    16. }
    17. else//右子树为空
    18. {
    19. Node* parent = _node->parent, * cur = _node;
    20. while (parent && cur == parent->right)//直到cur是parent的左子节点为止
    21. {
    22. cur = parent;
    23. parent = cur->parent;
    24. }
    25. _node = parent;
    26. }
    27. }

    ②迭代器-- 

    有了++的理解,--操作就简单了。

    还是以该树为例:
     也要分两种情况:
    第一种:有左子树,向下走。

    以根节点4为例,--之后不是节点2而是节点3。对于有左子树的节点而言,--之后的节点是左子树的最右子节点。

    第二种:没有左子树,向上走。

    对比节点3和节点5,节点3--之后是节点2,节点5--之后是节点4。可以看出,如果节点是父节点的右子节点,那么--之后就是父节点。但如果是左子节点,那么需要向上判断直到是父节点的右子节点为止。或者如节点1一样,一直判断到根节点为止。

    其原理就是如果是父节点的左子节点,那么说明父节点在自己之后,如果是右子节点那么说明在父节点之后。

    代码如下:

    1. Self operator--(int)
    2. {
    3. Self it(_node);
    4. goHead();
    5. return it;
    6. }
    7. void goHead()
    8. {
    9. if (_node->left)//有左子树
    10. {
    11. Node* right = _node->left;
    12. while (right->right)//直到左子树的最右子节点
    13. {
    14. right = right->right;
    15. }
    16. _node = right;
    17. }
    18. else//无左子树
    19. {
    20. Node* parent = _node->parent, * cur = _node;
    21. while (parent&& cur = parent->left)//直到cur是parent的右子节点
    22. {
    23. cur = parent;
    24. parent = cur->parent;
    25. }
    26. _node = parent;
    27. }
    28. }

    (二).begin&end&const

    迭代器的begin和end只能在rb_tree中实现,对于begin而言,返回红黑树最左节点的迭代器,end返回root的父节点迭代器(空节点迭代器)。

    对于const,首先我们应该知道不管map还是set均不支持对key的修改

    但在实现上map和set大有不同

    map采用在传入时就传入const key,而set采用将迭代器都使用底层const迭代器

    用几个小时来制定计划,可以节省几周的编程时间—— 未名


    如有错误,敬请斧正

  • 相关阅读:
    python 获取双色球开奖数据的实现
    HTTP详细讲解
    99%必用git指令
    Windows操作系统 | CMD命令行查看当前用户名
    【2023年11月第四版教材】第24章《法律法规与标准规范》(合集篇)
    关于前端按钮点击之后禁用的方法总结
    React 高频面试题1(答案和题目都是根据讯飞星火写的)
    【高级篇 / ZTNA】(7.0) ❀ 01. FortiClient EMS 下载与安装 ❀ FortiGate 防火墙
    【学习笔记】杜教筛
    jasypt组件死锁bug案例分享
  • 原文地址:https://blog.csdn.net/weixin_61857742/article/details/127975541