【STL】模拟实现map和set {map和set的封装;核心结构;插入和查找;红黑树的迭代器;STL中的红黑树结构}

news2024/11/26 1:30:05

模拟实现map和set

  • map和set是红黑树的两种不同封装形式,底层使用同一颗泛型结构的红黑树,只是存储类型不同。
  • set是红黑树的K模型,存储key;map是红黑树的KV模型,存储pair<key,value>。

下面的代码和讲解着重体现红黑树的底层实现和map\set上层封装的衔接。红黑树的具体结构,基本操作,实现原理等内容请阅读下面几篇文章:

  1. 【高阶数据结构】二叉搜索树 {概念;实现:核心结构,增删查,默认成员函数;应用:K模型和KV模型;性能分析;相关练习}
  2. 【STL】map和set的介绍和使用 {关联式容器;键值对;map和set;multimap和multiset;OJ练习}
  3. 【高阶数据结构】AVL树 {概念及实现;节点的定义;插入并调整平衡因子;旋转操作:左单旋,右单旋,左右双旋,右左双旋;AVL树的验证及性能分析}
  4. 【高阶数据结构】红黑树 {概念及性质;红黑树节点的定义;红黑树插入操作详细解释;红黑树的验证}

一、核心结构

  • 问题一:map和set底层使用同一颗泛型结构的红黑树,如何处理map(pair<key,value>)和set(key)存储值不同的问题?

    解决方案:泛型底层红黑树的存储类型,通过不同的实例化参数,实现出map和set。

  • 问题二:在进行查找、插入、删除操作时,要对key值进行比较。在同一模版中,如何区别比较map和set中的key值?

    解决方案:通过传入仿函数KofT(KeyofTree)解决。如果是set,KofT对象返回data的值;如果是map,KofT对象返回data.first的值;

    注意:pair中重载了关系运算符,但first和second都参与运算,不符合要求。要求只比较pair.first (key)。

1.1 RBTreeNode && RBTree

// RBTree.hpp
enum Color{
  RED,
  BLACK
};

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

  T _data; //泛型底层红黑树的存储类型,通过不同的实例化参数,实现出map和set。
  Color _color;
  RBTreeNode(const T &data = T(), Color color = RED)
    :_left(nullptr),
    _right(nullptr),
    _parent(nullptr),
    _data(data),
    _color(color)
  {}
};

//
// K: key的类型,
// T: 如果是map,则为pair<K, V>; 如果是set,则为K
// KofT: 通过T类型的data来获取key的一个仿函数类
template <class K, class T, class KofT>
class RBTree{
  typedef RBTreeNode<T> Node; //第二个模版参数T,决定红黑树的存储类型
  Node *_root = nullptr;
  //......
};

1.2 set & map封装

// Set.hpp
namespace zty{
  template <class K>
  class set{
    struct SetKofT{ //用于取出data中的key
      const K& operator()(const K &k){
        return k; 
      } 
    };
    typedef RBTree<K, K, SetKofT> RBT; //set是K模型的红黑树,只存储key值
    RBT _rbt;
    //......
  };
}

//
// Map.hpp
namespace zty{
  template <class K, class V>
    class map{
      struct MapKofT{ //用于取出data中的key
        const K& operator()(const pair<K,V>& kv){
          return kv.first;
        }
      };
      typedef RBTree<K, pair<K,V>, MapKofT> RBT; //map是KV模型的红黑树,存储pair<K,V>键值对
      RBT _rbt;
      //......
	};
}

二、插入和查找

2.1 RBTree::insert

// RBTree.hpp
template <class K, class T, class KofT>
pair<typename RBTree<K,T,KofT>::iterator, bool> RBTree<K,T,KofT>::Insert(const T &data)
{
  KofT kot; //创建KofT对象,用于取出data中的key
  if(_root == nullptr)
  {
    _root = new Node(data, BLACK);
    return make_pair(iterator(_root), true); //返回pair<iterator, bool>,方便实现operator[]。
  }

  Node *cur = _root;
  Node *parent = nullptr;
  while(cur != nullptr)
  {
    if(kot(data) > kot(cur->_data)) //不管是map还是set都能正确的取出key进行比较。
    {
      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,RED);
  Node *newnode = cur; //在调整红黑树的过程中cur的指向会改变,所以要提前记录新节点的指针。
  if(kot(data) > kot(parent->_data))
  {
    parent->_right = cur;
  }
  else{
    parent->_left = cur;
  }
  cur->_parent = parent;
  //上一次循环中grandparent 为根节点,此次循环parent == nullptr
  while(parent != nullptr && parent->_color == RED) 
  {
    //......具体内容请参考红黑树章节内容
  } //end of while
  
  if(cur == _root)
    cur->_color = BLACK;

  return make_pair(iterator(newnode), true); //返回pair<iterator, bool>,方便实现operator[]。
}

2.2 RBTree::find

//RBTree.hpp
template <class K, class T, class KofT>
typename RBTree<K,T,KofT>::iterator RBTree<K,T,KofT>::Find(const K &k){
  KofT kot;
  if(_root == nullptr)
  {
    return end();
  }
  Node *cur = _root;  
  while(cur != nullptr)
  {
    if(k > kot(cur->_data))
    {
      cur = cur->_right;
    }
    else if(k < kot(cur->_data))
    {
      cur = cur->_left;
    }
    else{
      return cur;
    }
  }
  return end();
}

2.3 set & map封装

//Set.hpp
namespace zty{
  template <class K>
  class set{
    //......
    bool Insert(const K& k){
      return _rbt.Insert(k).second;
    }

    iterator Find(const K& k){
      return _rbt.Find(k);
    }
  };
}

//
// Map.hpp
namespace zty{
  template <class K, class V>
    class map{
      //......
      pair<iterator, bool> Insert(const pair<K,V>& kv){
        return _rbt.Insert(kv);
      }
        
      V& operator[](const K& k){ //Insert返回pair<iterator, bool>,方便实现operator[]。
        pair<iterator, bool> ret = Insert(make_pair(k, V()));
        return ret.first->second;
      }

      iterator Find(const K& k){
        return _rbt.Find(k);
      }
    };
}

三、迭代器

问题三:map和set(红黑树)的迭代器如何实现?

在这里插入图片描述

  1. 红黑树的迭代器底层封装一个指向节点的指针,基本操作请参照list迭代器的实现。

  2. 红黑树迭代器的实现难点在于++和–操作。

  3. 通过二叉树的中序遍历规则得出:(中序:左子树,根,右子树)

    1. begin是中序遍历的第一个节点,即二叉树的最左(最小)节点。

    2. end是中序遍历最后一个节点的下一个位置(左闭右开),这里我们设为nullptr。

    3. ++操作:(中序:左子树,根,右子树)

      1. 如果当前节点的右子树不为空,++就是找右子树中序的第一个节点(最左节点)。

      2. 如果当前节点的右子树为空,++就是找孩子不是右节点的那个祖先

        提示:右子树为空或孩子是右节点,说明这棵子树已经遍历访问完了。

    4. –操作:和++相反(右子树,根,左子树)

      1. 如果当前节点的左子树不为空,–就是找左子树的最右节点。

      2. 如果当前节点的左子树为空,–就是找孩子不是左节点的那个祖先

        提示:左子树为空或孩子是左节点,说明这棵子树已经遍历访问完了。

3.1 RBT_iterator

// RBTree.hpp
template<class T, class Ref, class Ptr>
class RBT_iterator{
  typedef RBT_iterator<T, Ref, Ptr> iterator;
  typedef RBTreeNode<T> Node;
  Node *_pnode; //红黑树的迭代器底层封装一个指向节点的指针
public:
  //基本操作请参照list迭代器的实现,不做过多解释
  RBT_iterator(Node *pnode = nullptr)
  :_pnode(pnode)
  {}

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

  Ptr operator->() const{
    return &_pnode->_data;
  }
  
  bool operator==(const iterator &it) const{
    return _pnode == it._pnode;
  }

  bool operator!=(const iterator &it) const{
    return _pnode != it._pnode;
  }
    
  //红黑树的迭代器的实现难点在于++和--操作
  iterator& operator++(){
    if(_pnode->_right != nullptr) //如果当前节点的右子树不为空,++就是找右子树中序的第一个节点(最左节点)。
    {
      Node *left = _pnode->_right;
      while(left->_left != nullptr)
      {
        left= left->_left;
      }
      _pnode = left;
    }
    else{ //如果当前节点的右子树为空,++就是找孩子不是右节点的那个祖先。
      Node *parent = _pnode->_parent;
      Node *cur = _pnode;
      while(parent != nullptr && cur == parent->_right) //parent == nullptr表示遍历到尾
      {
        parent = parent->_parent;
        cur = cur->_parent;
      }
      _pnode = parent;
    }
    return *this;
  }

  iterator& operator--(){
    if(_pnode->_left != nullptr) //如果当前节点的左子树不为空,--就是找左子树的最右节点。
    {
      Node *right = _pnode->_left;
      while(right->_right != nullptr)
      {
        right = right->_right;
      }
      _pnode = right;
    }
    else{ //如果当前节点的左子树为空,--就是找孩子不是左节点的那个祖先。
      Node *parent = _pnode->_parent;
      Node *cur = _pnode;
      while(parent != nullptr && cur == parent->_left) //parent == nullptr表示遍历到头
      {
        parent = parent->_parent;
        cur = cur->_parent;
      }
      _pnode = parent;
    }
    return *this;
  }
};

//

template <class K, class T, class KofT>
class RBTree{
//......
public:
  typedef RBT_iterator<T, T&, T*> iterator; //普通迭代器
  typedef RBT_iterator<T, const T&, const T*> const_iterator; //const迭代器

  iterator begin(){ //begin是中序遍历的第一个节点,即二叉树的最左(最小)节点。
    Node *left = _root;
    while(left != nullptr && left->_left != nullptr)
    {
      left = left->_left;
    }
    return iterator(left);
  }

  iterator end(){
    return iterator(nullptr); //end是中序遍历最后一个节点的下一个位置(左闭右开),这里我们设为nullptr。
  }
};

3.2 set & map封装

// Set.hpp
namespace zty{
  template <class K>
  class set{
  //......
    typedef RBTree<K, K, SetKofT> RBT;   
  public:
    typedef typename RBT::iterator iterator; //set的迭代器类型
    typedef typename RBT::const_iterator const_iterator; //const迭代器

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

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

//
// Map.hpp
namespace zty{
  template <class K, class V>
    //......
      typedef RBTree<K, pair<K,V>, MapKofT> RBT;
    public:
      typedef typename RBT::iterator iterator; //map的迭代器类型
      typedef typename RBT::const_iterator const_iterator; //const迭代器

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

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

四、STL中的红黑树结构

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

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

在这里插入图片描述

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

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

相关文章

Vue进阶(三十三)Content-Security-Policy(CSP)详解

文章目录 一、前言二、XSS 攻击都有哪些类型&#xff1f;三、CSP介绍3.1 使用HTTP的 Content-Security-Policy头部3.2 使用 meta 标签 四、CSP 实施策略五、Vue中可使用的防XSS攻击方式六、拓展阅读 一、前言 作为前端工程师你真的了解 XSS 吗&#xff1f;越来越多超级应用基于…

预付费电表和断路器的连接方式及注意事项

随着智能电网技术的不断发展&#xff0c;预付费电表已经在我国得到了广泛应用。预付费电表不仅可以实现远程自动抄表、实时监控用电量等功能&#xff0c;还可以有效防止偷电行为&#xff0c;提高用电安全。断路器作为低压配电系统中的重要组成部分&#xff0c;具有保护电路、防…

详细介绍 弹性盒子(display:flex)

文章目录 什么是弹性盒子 如何使用弹性盒子flex系列flex-direction 对齐方向 水平对齐垂直对齐flex-wrap 换行flex-flowflex模型说明容器的属性 justify-content X轴对齐方式align-content Y轴对齐方式总结属性值Y轴对齐的另外一种&#xff1a;align-itemsalign-content和alig…

基于Springboot跟rabbitmq实现的死信队列

概述 RabbitMQ是流行的开源消息队列系统&#xff0c;使用erlang语言开发。为了保证订单业务的消息数据不丢失&#xff0c;需要使用到RabbitMQ的死信队列机制&#xff0c;当消息消费发生异常时&#xff0c;将消息投入死信队列中。但由于对死信队列的概念及配置不熟悉&#xff0…

自然语言处理实战项目17-基于多种NLP模型的诈骗电话识别方法研究与应用实战

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下自然语言处理实战项目17-基于NLP模型的诈骗电话识别方法研究与应用&#xff0c;相信最近小伙伴都都看过《孤注一掷》这部写实的诈骗电影吧&#xff0c;电影主要围绕跨境网络诈骗展开&#xff0c;电影取材自上万起真…

PPO代码研究(2)

好&#xff0c; 因为我没怎么看懂&#xff0c; 所以我决定再看一遍PPO的代码&#xff0c; 再研究一遍。 事实证明&#xff0c; 重复是一个非常好&#xff0c;非常好的方法。 学习方法。 世界上几乎没有任何新知识是你一遍就能学会的。 你只能学一遍&#xff0c;再来一遍&…

大剧院订座系统源码,大剧院订票,大剧院场馆租赁,大剧院订票系统完整源码

大剧院订座系统源码,大剧院订票&#xff0c;大剧院场馆租赁&#xff0c;大剧院订票系统完整源码 大剧院系统1、管理后台--系统说明2、订票小程序--系统说明3、验票端--系统说明4、系统源码说明 大剧院系统 1、管理后台–系统说明 项目管理&#xff1a;用于创建剧院演出项目 2…

【广州华锐互动】AR技术在配电系统运维中的应用

随着科技的不断发展&#xff0c;AR(增强现实)技术逐渐走进了我们的生活。在电力行业&#xff0c;AR技术的应用也为巡检工作带来了许多新突破&#xff0c;提高了巡检效率和安全性。本文将从以下几个方面探讨AR配电系统运维系统的新突破。 首先&#xff0c;AR技术可以实现虚拟巡检…

Qt应用开发(基础篇)——按钮基类 QAbstractButton

一、前言 QAbstractButton类&#xff0c;继承于QWidget&#xff0c;是Qt按钮小部件的抽象基类&#xff0c;提供按钮常用的功能。 QAbstractButton按钮基类&#xff0c;它的子类(pushbutton、checkbox、toolbutton等)处理用户操作&#xff0c;并指定按钮的绘制方式。QAbstractBu…

el-table中加图标文字提示

<el-table :data"tableData" style"width: 100%" max-height"250"><el-table-column fixed prop"aaa" label"日期" width"150" /><el-table-column prop"bbb" label"日期" wi…

英语语法基础--思维导图

思维导图通常用于可视化和整理信息&#xff0c;而英文语法非常广泛且复杂&#xff0c;无法在一个简单的思维导图中完整表示。然而&#xff0c;我可以提供一个简化版本的英文语法思维导图&#xff0c;列出一些主要的语法概念和部分示例。 请注意&#xff0c;这只是一个基本的概…

多个pdf怎么合并在一起?跟着我的步骤一起合并

多个pdf怎么合并在一起&#xff1f;利用PDF文档合并功能可以帮助您更有效地管理文件&#xff0c;将多个相关文件整合成一个文件&#xff0c;避免分散在多个文件中。此外&#xff0c;合并后的文件更便于共享和传输&#xff0c;因为只需共享一个文件而不是多个文件。虽然合并文件…

自学Python01-创建文件写入内容

此处省去安装和前言&#xff0c;需要两个东西 一个去下载安装python官方库 Welcome to Python.org 一个是编译器pycharm PyCharm 安装教程&#xff08;Windows&#xff09; | 菜鸟教程 PyCharm: the Python IDE for Professional Developers by JetBrains 第一节 练习print…

18--Elasticsearch

一 Elasticsearch介绍 1 全文检索 Elasticsearch是一个全文检索服务器 全文检索是一种非结构化数据的搜索方式 结构化数据&#xff1a;指具有固定格式固定长度的数据&#xff0c;如数据库中的字段。 非结构化数据&#xff1a;指格式和长度不固定的数据&#xff0c;如电商网站…

rocky(centos) 安装redis,并设置开机自启动

一、下载并安装 1、官网下载Redis 并安装 Download | RedisRedisYou can download the last Redis source files here. For additional options, see the Redis downloads section below.Stable (7.2)Redis 7.2 …https://redis.io/download/ 2、上传下载好的redis压缩包到 /…

电气工程中重要的测量术语:“kVRMS” | 百能云芯

在电气工程和电子领域&#xff0c;术语“kVRMS”至关重要。它是工程师和技术人员用来准确评估电气系统电压的关键测量方法。在这篇综合文章中&#xff0c;我们将深入探讨 kVRMS 的含义、其意义、应用。 kVRMS 代表“千伏均方根”。为了理解这个术语&#xff0c;我们来分解一下&…

【Java Web】统一处理异常

一个异常处理的ControllerAdvice类。它用于处理Controller注解的控制器中发生的异常。 具体代码功能如下&#xff1a; 导入相关类和方法。声明一个Logger对象&#xff0c;用于日志记录。使用ExceptionHandler注解标记handleException方法&#xff0c;用于处理所有异常。 -嘛在…

管网水位监测的必要性

城市燃气、桥梁、供水、排水、热力、电力、电梯、通信、轨道交通、综合管廊、输油管线等&#xff0c;担负着城市的信息传递、能源输送、排涝减灾等重要任务&#xff0c;是维系城市正常运行、满足群众生产生活需要的重要基础设施&#xff0c;是城市的生命线。基础设施生命线就像…

centos+jenkins+pycharm

思路&#xff1a;架构 一. 在centos上搭建jenkins环境 二. pycharm与gitee建立连接 三. 访问jenkins&#xff0c;添加任务 3.1 添加一个自由风格的任务 3.2 添加git项目路径及访问git的账号和密码 3.3 执行start.sh脚本 四. 浏览器访问jenkins执行任务

leetcode-779. 第K个语法符号(java)

第K个语法符号 题目描述递归代码演示 题目描述 难度 - 中等 LC- 779. 第K个语法符号 我们构建了一个包含 n 行( 索引从 1 开始 )的表。首先在第一行我们写上一个 0。接下来的每一行&#xff0c;将前一行中的0替换为01&#xff0c;1替换为10。 例如&#xff0c;对于 n 3 &#…