C++之map和set 的封装

通过红黑树的学习(C++之红黑树-CSDN博客)让我了解到map和set的底层如何实现,这一次我们来对map和set进行封装。

目录

1.map和set底层原理

 2.map和set的定义

 3.map和set的仿函数

4.map和set的插入

5.map和set的迭代器

5.1迭代器的构造

5.2解引用

5.3成员访问操作符

5.4 == 与!=

5.5begin()

5.6end()

5.7++

 5.8--

 6.map总体代码

7.set总体代码


1.map和set底层原理

 我们都知道map和set的底层都是红黑树,但是map是kv模型,set是k模型,对于红黑树来说这两种不同的模型 可以通过控制模版参数来区分。

map简化后的源码

template<class Key,class T,class Compare = less<Key>,class Alloc=alloc>
class map
{
public:typedef Key ket_type;typedef T data_type;typedef pair<const Key, T> value_type;
private:typedef rb_tree < key_type, value_type,selectlst<value_type>, key_compare, Alloc> rep_type;
};

set简化后的源码:

template<class Key, class Compare = less<Key>, class Alloc = alloc>
class set
{
public:typedef Key ket_type;typedef Key data_type;typedef Compare key_type;typedef Compare value_compare;
private:typedef rb_tree < key_type, value_type,identity<value_type>, key_compare, Alloc> rep_type;
};

 那么红黑树是怎么通过模板来区分map和set 的呢?


rb_tree 的 value 决定 是map的key/value 还是 set的key:

如果是set容器,那么它传入底层红黑树的模板参数就是Key和Key:

    template<class K>class set{private:RBTree<K,K> _t;};

如果是map容器,传入底层红黑树的模板参数就是Key和Key和value的键值对:

    class map{private:RBTree<K, pair<const K,V>> _t;};

通过上面,我们可以知道,对于set和map的区别:我们只要通过第二个模板参数就能进行区分那是不是第一个模板参数就没有意义了呢?

    对于insert(const Value&v)来说,需要放入存入的值,确实是这个样子的,插入的值是value,对于set就是key,对于map就是pair。
    但是对于find(const Key&key)来说,查找的参数不是value,找的不是pair而是Key,对于map容器来说就不行了。

 2.map和set的定义

set:

template<class K>
class set
{
private:
RBTree<K,K> _t;
};

map:

class map
{
private:
RBTree<K, pair<const K,V>> _t;
}

 3.map和set的仿函数

我们将传入的data直接进行比较

Q:

对于set是Key,可以比较
 对于map是pair,我们无法直接进行比较,我们的期望是使用pair键值对中的first去比较,与second无关。

由于底层的红黑树不知道传的是map还是set容器,当需要进行两个结点键值的比较时,底层红黑树传入的仿函数来获取键值Key,进行两个结点键值的比较:这个时候我们就需要仿函数了,如果是set那就是用于返回T当中的键值Key,如果是map那就是用于返回pair的first:

仿函数/函数对象也是类,是一个类对象。仿函数要重载operator() 

map:

namespace bt
{template<class K, class V>class map{struct MapkeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};
}

set:

namespace bt
{template <class K>class set  // 仿函数,重载operator(){struct SetKeyOfT{const K& operator()(const K& key){return key;}};
}

 

 我们的查找函数如下:

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;}
}

4.map和set的插入

直接cv大法,从红黑树那里拿到了代码

//map
bool insert(const pair<K, V>& kv)
{return _t.Insert(kv);
}//set
bool insert(const K& key)
{return _t.Insert(key);
}

5.map和set的迭代器

5.1迭代器的构造

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){}
}

5.2解引用

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

5.3成员访问操作符

Ptr operator->(){return &_node->_data;}

5.4 == 与!=

  bool operator !=(const Self & s) const{return _node != s._node;}bool operator ==(const Self& s) const{return _node == s._node;}

5.5begin()

begin():返回中序(左、根、右)第一个结点的正向迭代器,即最左节点,返回的是最左节点,直接找最左节点即可

template<class K, class T,class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef __RBTreeIterator<T> iterator;iterator begin(){Node* left = _root;while (left && left->_left){left = left->_left;}return iterator(left);}
}

5.6end()

end():返回中序(左、根、右)最后一个结点下一个位置的正向迭代器,这里用空指针

template<class K, class T,class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef __RBTreeIterator<T> iterator;iterator begin(){Node* left = _root;while (left && left->_left){left = left->_left;}return iterator(left);}iterator end(){return iterator(nullptr);}
}

5.7++

	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;}

一个结点的正向迭代器进行++操作后,根据红黑树中序(左、根、右)找到当前结点的下一个结点,中序的第一个节点是最左,迭代器的++怎么去找:

  • 如果节点的右子树不为空++就是找右子树的最左节点
  • 如果节点的右子树为空++就是找祖先(孩子是父亲的左的那个祖先

 5.8--

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;}

如果是根,–就是左子树,找到左子树最大的那一个(最右节点)

  • 如果节点的左子树不为空,--找左子树最右的节点
  • 如果节点的左子树为空,--找祖先(孩子是父亲的右的祖先)

 6.map总体代码

#include"RBTree.h"namespace bt
{template<class K, class V>class map{struct MapkeyOfT {const K& operator()(const pair<const K, V>& kv){return kv.first;}};public://typename:没有实例化的模板,区分不了是静态变量还是类型,typename告诉编译器是类型typedef typename RBTree<K, pair<const K, V>, MapkeyOfT>::iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapkeyOfT>::const_iteratorconst_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;};}

7.set总体代码

#include"RBTree.h"namespace bt
{template <class K>class set  // 仿函数,重载operator(){struct SetKeyOfT{const K& operator()(const K& key){return key;}};public://typename:没有实例化的模板,区分不了是静态变量还是类型,typename告诉编译器是类型typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;//key不可以修改typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;iterator begin() const{return _t.begin();}iterator end() const{return _t.end();}pair<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);//用普通迭代器构造const迭代器}private:RBTree<K, K, SetKeyOfT> _t;};}

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

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

相关文章

Maven修改本地仓库的默认路径

前言 忽然发现当时下载Maven时候的默认路径没有与Maven放到同一个文件夹内&#xff0c;强迫症的我就想着修改一下Maven的路径&#xff01;也方便后续我的jar包管理&#xff0c;放到C盘下可能会导致占用C盘空间等等问题吧。以下是我的操作过程&#xff1a; 一、创建一个本地仓库…

QX---mini51单片机学习---(6)独立键盘

目录 1键盘简绍 2按键的工作原理 3键盘类型 4独立键盘与矩阵键盘的特点 5本节相关原理图 6按键特性 7实践 1键盘简绍 2按键的工作原理 内部使用轻触按键&#xff0c;常态按下按键触点才闭合 3键盘类型 编码键盘与非编码键盘 4独立键盘与矩阵键盘的特点 5本节相关原理…

GStreamer中如何自定义配置线程优先级

1.引言 如果看了gstreamer官方教程配置多线程出现编译不过的问题了&#xff0c;不妨进来看看这篇文章或许能解决一些编译问题。 GStreamer 本质上是多线程的&#xff0c;并且是完全线程安全的。大多数线程内部对应用程序是隐藏的&#xff0c;这应该使应用程序开发更容易。但是&…

RabbitMQ--死信队列

目录 一、死信队列介绍 1.死信 2.死信的来源 2.1 TTL 2.2 死信的来源 3.死信队列 4.死信队列的用途 二、死信队列的实现 1.导入依赖 pom.xml 2.application.properties 3.配置类 4.生产者 5.业务消费者&#xff08;正常消费者&#xff09; 6.死信队列消费者 一、…

Linux系统安全整改实践指南

在当前信息化高速发展的时代&#xff0c;Linux操作系统凭借其开源、稳定和高效的特点&#xff0c;在服务器市场占据着举足轻重的地位。然而&#xff0c;随着网络威胁的日益复杂化&#xff0c;确保Linux系统的安全性成为了一项至关重要的任务。本文旨在提供一套全面的Linux系统安…

leetcode 1319.连通网络的操作次数

思路&#xff1a;DFS&#xff08;连通块&#xff09; 其实一开始的时候&#xff0c;并不知道这道题的精髓在哪&#xff0c;总想着&#xff0c;啊&#xff1f;这怎么用图论的思想做啊&#xff1f; 细细思考之后&#xff0c;这道题还是比较有意思的&#xff0c;需要有一定的数据…

# Mysql 数据库区分大小写吗?

Mysql 数据库区分大小写吗&#xff1f; 1、MySQL 数据库在区分大小写方面有特定的行为&#xff0c;这取决于多个因素&#xff0c;包括操作系统、配置参数以及使用的字符集。 2、数据库名和表名&#xff1a; 在 Linux 系统中&#xff0c;数据库和表名是严格区分大小写的。 而…

【前端性能优化】深入解析重绘和回流,构建高性能Web界面

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 &#x1f3af; 引言&#xff1a;探索Web性能的基石&#x1f3d7;️ 基础概念&#xff1a;什么是重绘和回流&#xff1f;&#x1f4cc; 回流&#xff08;Reflow&#xff09;&#x1f4cc; 重绘&#xff08;Repaint&#xff0…

蓝桥杯国赛每日一题:交换瓶子(图论,环,贪心)

题目描述&#xff1a; 有 N 个瓶子&#xff0c;编号 1∼N&#xff0c;放在架子上。 比如有 5 个瓶子&#xff1a; 2 1 3 5 4要求每次拿起 2 个瓶子&#xff0c;交换它们的位置。 经过若干次后&#xff0c;使得瓶子的序号为&#xff1a; 1 2 3 4 5对于这么简单的情况&#…

使用Flask部署Web应用:从入门到精通

文章目录 第一部分&#xff1a;准备工作第二部分&#xff1a;部署Flask应用到AWS部署到AWS Lambda 第三部分&#xff1a;部署Flask应用到腾讯云服务器部署到腾讯云服务器 第四部分&#xff1a;优化和扩展结论 在现代软件开发中&#xff0c;Web应用的部署是一个至关重要的环节。…

使用Flask-SocketIO构建实时Web应用

文章目录 准备工作编写代码编写HTML模板运行应用 随着互联网的发展&#xff0c;实时性成为了许多Web应用的重要需求之一。传统的HTTP协议虽然可以实现实时通信&#xff0c;但是其长轮询等机制效率低下&#xff0c;无法满足高并发、低延迟的需求。为了解决这一问题&#xff0c;诞…

python常见数据的存取

python数据的存取 python数据的存取数据的保存3.1.1 保存list3.1.2 保存Dict3.1.3 保存Set3.1.4 保存Dataframe3.1.5 保存Matrix 3.2 数据的读取3.2.1 读取txt文件中的数据3.2.2 读取excel文件中的数据3.2.3 读取csv文件中的数据3.2.4 读取stata文件中的数据3.2.5 读取R文件中的…

计算机发展史故事【14】

大象踢踏舞 如果要把电脑50 年的历史划分为两个不同的阶段&#xff0c;那么&#xff0c;1981 年无疑是个分界线。就在那一年&#xff0c;IBM 公司推出个人电脑PC 机&#xff0c;使人类社会大步跨进个人电脑新时代。今天&#xff0c;全世界正在使用的PC 机已达到2 亿台&#xf…

视频拼接融合产品的产品与架构设计(三)内存和显存单元数据迁移

上一篇文章 视频拼接融合产品的产品与架构设计(二) 这一篇沉下先来&#xff0c;彻底放弃了界面&#xff0c;界面最终的体现是最后要做的&#xff0c;现在要做的是产品的架构&#xff0c;使用链式架构方式迁移数据。同时增加插件口&#xff0c;方便编程序。 插件架构 为了视频…

Android 开机过程画面

Android 开机画面流程 Android 开机动画加载流程涉及bootloader、内核、Android 核心进程、Android文件系统 Bootloader(引导加载程序):当设备启动时,首先由 Bootloader 加载。Bootloader 位于设备的固化存储器中,其主要功能是初始化硬件并启动操作系统。 内核加载:Boo…

Kivy 项目51斩百词 3 屏幕页面转换

MRWord/pages/indexpage/index.py class IndexPage(GridLayout):# 初始化def __init__(self, **kwargs):super().__init__(**kwargs)staticmethoddef index_to_upload():App.get_running_app().screen_manager.current "Upload"定义了一个名为 IndexPage 的类&…

短剧奔向小程序,流量生意如何开启?

随着移动互联网的飞速发展&#xff0c;小程序作为一种轻量级、易传播的应用形态&#xff0c;逐渐在各个领域展现出其独特的商业价值。而最近爆火的短剧小视频作为一种受众广泛的娱乐形式&#xff0c;与小程序结合后&#xff0c;不仅为观众提供了更为便捷的观看体验&#xff0c;…

代码随想录算法训练营第五十三天|LeetCode1143.最长公共子序列、LeetCode1035.不相交的线、LeetCode53.最大子序和

LeetCode 1143 最长公共子序列 题目链接&#xff1a;1143. 最长公共子序列 - 力扣&#xff08;LeetCode&#xff09; 【解题思路】 1.确定dp数组含义 dp[i][j] &#xff1a;长度为[0,i-1]的字符串和长度为[0,j-1]的字符串的最长公共子序列为dp[i][j] 2.确定递推公式 text1[i…

Linux线程(三)死锁与线程同步

目录 一、什么是死锁 死锁的四个必要条件 如何避免死锁 避免死锁算法 二、Linux线程同步 三 、条件变量 1、条件变量基本原理 2、条件变量的使用 3、条件变量使用示例 为什么 pthread_cond_wait 需要互斥量? 一、什么是死锁 死锁是计算机科学中的一个概念&#xff0c;…