【C++】map和set的封装

目录

    • 前言
    • 一、红黑树的设计
      • 1.1 红黑树存储节点的设计
      • 1.2 红黑树的迭代器
      • 1.3 map的设计
      • 1.4 set的设计
      • 1.5关于map与set的const_iterator设计


前言

我们知道map和set的底层都是用红黑树实现的,但是set和map的结构不一样,set只有一个参数K,而map有两个参数K/V,难道set和map是利用俩份红黑树来实现的吗?从设计者的角度是不可能这样复现的,这样实现的话两个容器有着大量的重复代码,只是参数的个数不同就要使用两份红黑树复现是完全不符合设计规则的,那么红黑树做了怎样的处理使得传入参数的不同来复现这两个容器呢?下面我们就一起来进行学习!!

一、红黑树的设计

1.1 红黑树存储节点的设计

既然map和set的参数不同,那么红黑树就要统一的对它们的参数进行处理,当为pair<K,V>类型时复现map,为K类型时复现set。我们先来看看stl库中是如何来进行设计的:

在这里插入图片描述

通过上图可以看到,map 传给红黑树的key_type是Key,value_type是pair<Key, T>;而 set 传给红黑树的key_type和value_type都是Key。红黑树节点中存储的数据就是value_type类型的数据。如果传给value_type的是pair<Key, T>,那么就会实例化出 map;而传给value_type的是Key,就会实例化出 set。

那么我们下面的问题就转化成了,如何去提取出value_type类型的数据?

在插入节点的时候我们是需要根据二叉搜索树的规则来调整的,set的Value为K直接提取出来对比即可,而map中的Vaule为pair<K, V>,我们只需提取出K来对比即可,stl库中对pair的排序规则不符合并我们的要求,所以我们得自己设计一个仿函数来返回对应的数据!!

在这里插入图片描述

红黑树节点存储设计如下:

#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), _col(RED){}
};// 仿函数
template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:bool Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return 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 false;}}// ......return true;}private:Node* _root = nullptr;
};

1.2 红黑树的迭代器

我们知道红黑树本质上也是一颗二叉搜索树,所以通过中序遍历能得到一个升序的序列。

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){return _node != s._node;}Self& operator++(){ // 当节点的右子树存在时, 一直往下沿左走 走到尽头了 证明左子树走完了 该走右子树了 右子树也重复该步骤if (_node->_right){Node* subL = _node->_right;while (subL->_left){subL = subL->_left;}_node = subL;}else{Node* cur = _node;Node* parent = cur->_parent;// 右子树走完了  我们应该返回到该右子树的父节点的祖先了while (parent && parent->_right == cur){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}Self& operator--(){if (_node->_left){Node* subR = _node->_left;while (subR->_right){subR = subR->_right;}_node = subR;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left == cur){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}
};template<class K, class T, class KeyOfT>
struct RBTree
{typedef RBTreeNode<T> Node;typedef __RBTreeIterator<T, T&, T*> iterator;
public:// begin()为树的最左节点iterator begin(){// 注: 需要避免_root为nullptrNode* left = _root;while (left && left->_left){left = left->_left;}return iterator(left);}iterator end(){return iterator(nullptr);}private:Node* _root = nullptr;
}

1.3 map的设计

#pragma once
#include "RBTree.h"namespace curry
{template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;   // 提取出kv的key值}};public:typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}V& operator[](const K& key){pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));return ret.first->second;}pair<iterator, bool> insert(const pair<const K, V>& kv){return _t.Insert(kv);}private:RBTree<K, pair<K, V>, MapKeyOfT> _t;  // 底层调用RBTree进行插入操作};
};

1.4 set的设计

#pragma once
#include "RBTree.h"namespace curry
{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;};
};

通过上述map和set的设计我们可以发现,实际它们的迭代器与功能操作都是由红黑树提供的!!

在这里插入图片描述

1.5关于map与set的const_iterator设计

我们先来看看stl库中对它的设计:

在这里插入图片描述

由上图可知,实际上在stl库中map/set的普通迭代器都设计成了const迭代器,那么const迭代器是如何实现的呢?

它是利用普通迭代器构造出来的。self与iterator是不一样的,我们的普通迭代器是self设计出来的,而const迭代器需要普通迭代器的构造,那么我们就需要显式定义出普通迭代器的类型进行构造。

在这里插入图片描述

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

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

相关文章

前端基础:1-2 面向对象 + Promise

面向对象 对象是什么&#xff1f;为什么要面向对象&#xff1f; 通过代码抽象&#xff0c;进而藐视某个种类物体的方式 特点&#xff1a;逻辑上迁移更加灵活、代码复用性更高、高度的模块化 对象的理解 对象是对于单个物体的简单抽象对象是容器&#xff0c;封装了属性 &am…

Linux_应用篇(07) 系统信息与系统资源

在应用程序当中&#xff0c;有时往往需要去获取到一些系统相关的信息&#xff0c;譬如时间、日期、以及其它一些系统相关信息&#xff0c;本章将向大家介绍如何通过 Linux 系统调用或 C 库函数获取系统信息&#xff0c; 譬如获取系统时间、日期以及设置系统时间、日期等&#x…

三能一体运营体系助力政企支撑水平提升

生产力的发展是现代社会孜孜不倦的追求&#xff0c;由此产生了我们熟悉的“机械化、电子化、信息化”乃至现今正在发生的“智能化”四次工业革命。这些是由技术的突破性发展带来的&#xff0c;但我们也注意到生产力发展的另一个助力&#xff0c;即生产效率的提升&#xff0c;19…

【MySQL数据库】mysql日志管理、备份与恢复

mysql日志管理、备份与恢复 MySQL数据库备份及日志一、数据库备份分类&#xff1a;如何选择逻辑备份策略 (频率)完全备份与恢复备份恢复 增量备份与恢复实现增量备份 基于时间点与位置恢复 二.MySQL日志管理 MySQL数据库备份及日志 在生产环境中&#xff0c;数据的安全性是至关…

在未来你将何去何从?

在数字化的浪潮中&#xff0c;信息技术行业无疑是推动全球经济和社会发展的重要动力。随着科技的不断迭代与进步&#xff0c;云计算、大数据、人工智能&#xff08;AI&#xff09;、物联网&#xff08;IoT&#xff09;、5G通信和区块链等技术已经深入到我们生活的每一个角落&am…

鸿蒙原生应用元服务开发-鸿蒙真机运行项目实战与注意事项

一、解压项目注意项目包不能为中文 二、用数据线将装好DevEco Studio的电脑与设置为开发者模式的鸿蒙手机相连接。 三、将项目包托进DevEco Studio 中 注意项目包文件不能有嵌套 四、查看设备运行 五、点击项目结构 六、勾选红色框圈部分 登录开发者账号 七、选择好公司 八、等…

我是如何使用 Next.js14 + Tailwindcss 重构个人项目的

前言 去年在学习 React 和 Nest 的时候&#xff0c;参考了大佬 imsyy 的项目 DailyHot&#xff0c;以此项目的灵感基于 React 开发&#xff0c;完成之后就没怎么在意。 后来发现这个项目还有点小流量&#xff0c;每天差不多 200-400 的 IP 访问量&#xff1a; 我又抽时间优…

深度学习之基于Pytorch框架手写数字识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 手写数字识别是数字图像处理领域的一个经典问题&#xff0c;也是深度学习技术的一个常用应用场…

uniappx 获取设备唯一标识(OAID、AAID、AndroidID、IMEI等) Ba-IdCode-U

简介&#xff08;下载地址&#xff09; Ba-IdCode-U 是一款可以获取国内各大手机厂商 OAID&#xff08;开放匿名设备标识&#xff09;及海外手机平台 AAID&#xff08;安卓广告标识&#xff09;的uniapp插件。另外也支持获取 IMEI/MEID、AndroidID、WidevineID、PseudoID、GUI…

Spring Cloud Alibaba-06-Sleuth链路追踪

Lison <dreamlison163.com>, v1.0.0, 2024.4.03 Spring Cloud Alibaba-06-Sleuth链路追踪 文章目录 Spring Cloud Alibaba-06-Sleuth链路追踪为什么使用链路追踪常见链路追踪解决方案Sleuth概述概述Sleuth术语 Sleuth Zipkin 原理Sleuth原理简述Zipkin 原理简述 Sleut…

代码随想录——路径总和(Leetcode113)需要回顾

题目链接 递归 本题递归需要遍历整棵树&#xff0c;所以递归没有返回值 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* T…

苹果M4性能分析:进步神速?还有多少空间?

2024年初&#xff0c;苹果推出了M4处理器&#xff0c;令人意外的是&#xff0c;它的发布距离M3发布仅仅过去了半年时间。更让人惊讶的是&#xff0c;M4首次亮相于iPad Pro。这一新处理器不仅仅是M3的简单升级版本&#xff0c;而是一次全面的架构优化。本文将详细分析M4处理器的…

Vue基础(1)数据绑定

一. 文本插值 普通文本可以使用双大括号 {{ }} &#xff0c;要想插入 HTML&#xff0c;需要使用 v-html 指令。 <template><h1>Message: {{ state.msg }}</h1><p>{{ state.count 1 }}</p><p>{{ state.rawHtml }}</p><p v-html…

【教学类-58-02】黑白三角拼图02(3*3宫格)262144种

背景需求&#xff1a; 已知黑白三角拼图2*2&#xff08;4个拼图&#xff09;一共有256种排列方法 【教学类-58-01】黑白三角拼图01&#xff08;2*2宫格&#xff09;256种-CSDN博客文章浏览阅读142次&#xff0c;点赞5次&#xff0c;收藏12次。【教学类-58-01】黑白三角拼图01…

深度学习之基于Matlab卷积神经网络(CNN)手写数字识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 手写数字识别是计算机视觉领域的一个重要问题&#xff0c;也是深度学习应用的一个典型场景。卷…

什么是固态继电器?

固态继电器是不需要使用任何机械部件的开关继电器。这通常使它们具有比普通机电继电器寿命更长的优势&#xff0c;然而&#xff0c;尽管固态继电器速度快且耐用&#xff0c;但仍具有某些设计规定。 固态继电器风靡全球&#xff0c;彻底改变了从农业自动化到航空航天等各个行业…

银行总部文件自动下发,如何保证不影响专线网络使用?

银行在我国金融体系中占据重要地位&#xff0c;是我国市场经济的重要组成部分。我国商业银行随着自身不断发展&#xff0c;规模日益扩大&#xff0c;形成了“总行-分行-支行-营业网点”的典型层级管理模式。在日常中&#xff0c;银行总部存在文件下发的场景&#xff1a; 银行总…

多家知名媒体到访“光子1号金融算力中心“ 交流AI与算力未来观

5月23日&#xff0c;企商在线 “光子1号金融算力中心媒体参观日”活动成功举办&#xff0c;十多家主流媒体、IT行业媒体及自媒体代表走进光子1号金融算力中心&#xff0c;深入了解企商业务发展、战略规划及“光子1号金融算力中心”等企商打造的新型数字基础设施&#xff0c;共同…

Python条件分支与循环

大家好&#xff0c;当涉及到编写高效和灵活的程序时&#xff0c;条件分支和循环是 Python 中至关重要的概念。它们允许我们根据不同的条件执行不同的代码块&#xff0c;或者重复执行一组语句。条件分支和循环是测试开发工程师在日常工作中经常使用的工具&#xff0c;无论是编写…

光耦合器的特性和应用概述

光耦合器又称光电耦合器&#xff0c;是现代电子学中必不可少的元件&#xff0c;确保隔离电路之间安全有效的信号传输。本文探讨了光耦合器的特性及其多样化应用&#xff0c;强调了它们在各种电子系统中的关键作用。 什么是光耦合器&#xff1f; 光耦合器是一种设计用于利用光传…