map与set

set使用

 set在我们就是我们前面学习的k模型,它可以用来比对数据,增删查的时间复杂度都是O(logn)效率非常高,由于它底层的原因,它也可以实现排序,通过中序遍历可以输出我们的有序的数据;又因为它的T值在插入的时候具有唯一性的原因,set还有去重的作用;

set的接口

参数:

set的构造函数

set有无参的,迭代器区间的,拷贝构造函数;

set的迭代器

 set的迭代器都是双端迭代器

接下来,我们使用一些代码来试验一下set的这些接口:

int main()
{int arr[] = { 8,6,2,4,5,3,1,7,9 };set<int> s_range(arr, arr + 9);cout << "s_range: ";for (auto x : s_range){cout << x << " ";}cout << endl;set<int> s_empty;s_empty.insert(5);s_empty.insert(8);s_empty.insert(4);s_empty.insert(9);s_empty.insert(7);s_empty.insert(3);set<int>::iterator ret = s_empty.begin();cout <<"s_empty: ";while (ret != s_empty.end()){cout << *ret << " ";ret++;}cout << endl;set<int> s_copy(s_empty);cout << "s_copy: ";for (auto x : s_copy){cout << x << " ";}cout << endl;
}

可以看到我们可以使用三种不同的方式构造 ;

set的增删查接口

下面是set常用的接口,需要了解这些,可以到set - C++ Reference (cplusplus.com)网站上面清楚明了的查看;

我感觉set,map这些接口了解大致使用就好了,所以我不打算过多的描述;

set中还有一个multiset

multiset是允许键值冗余的,也就是说,当有相同元素插入的时候,是可以正常插入的multiset中的;其中我们如果我们使用find接口寻找这个值的时候,寻找的是中序遍历的第一个值;

实验:键值冗余时find寻找的元素

int main()
{int arr[] = { 5,3,7,2,4,6,6,6,8 };multiset<int> s;for (auto x : arr){s.insert(x);}cout << "全部元素:";for (auto x : s){cout  << x << " ";}cout << endl;auto ret=s.find(4);

我们可以看到如果是从中序的第一个那么就会find我们全部的6,如果非中序,那么输出的也就不是全部的6了;所以证明我们find是中序遍历的;

map使用

map存放了两个数据 ,它就是前面学的kv模型;一般我们使用map获得的返回值都是pair类型的,因为需要返回k,v两个值,所以将他们封装在了我们的一个类pair中;

map操作:

int main()
{map<int, string>m;m.insert(make_pair(3, "wangwu"));m.insert(make_pair(4, "zhaoliu"));m.insert(make_pair(1, "zhangsan"));m.insert(make_pair(2,"lisi"));for (auto x : m){cout << x.first << ":" << x.second << endl;}return 0;
}

这里不再对map的接口做讲解,只放上简单的接口以及链接;因为stl的使用都是差不多的;

下面就是map常用的接口

map常用的接口

详细信息查看:map::map - C++ Reference (cplusplus.com) 

[]运算符重载

map最需要了解的就是[]运算符重载;

我们可以通过第一个key值来修改第二个value,也可以通过[key]直接插入我们的key到map中;

我们可以将它的操作看成这样

 我们通俗的可以翻译成这样;

这是写在map中的重载
V& operator[](const K& key)
{pair ret=this->insert(make_pair(key, V()));return ret.first.second;
}

其实就是先通过调用insert函数插入key,再通过insert的返回值获得key对应的value的引用,从而可以直接修改我们的value;

操作:

int main()
{map<int, string>m;m.insert(make_pair(3, "wangwu"));m.insert(make_pair(4, "zhaoliu"));m.insert(make_pair(1, "zhangsan"));m.insert(make_pair(2,"lisi"));for (auto x : m){cout << x.first << ":" << x.second << endl;}cout << "----------------------------"<<endl;m[5];//插入操作,我们没有修改任何数据,但是map中没有这个元素5所以会直接插入for (auto x : m){cout << x.first << ":" << x.second << endl;}cout << "----------------------------"<<endl;m[5] = "chengqi";//修改value操作for (auto x : m){cout << x.first << ":" << x.second << endl;}cout << "----------------------------" << endl;return 0;
}

上面就是对map和set使用的介绍

接下来我们实现一下我们的map和set;

map与set的实现

(我们这里只是实现了map与set的插入接口和迭代遍历接口)

map与set是两个具有排序作用的高效率容器,并且map与set的底层都是红黑树,由于红黑树在进行平衡树时检查严格程度放松一些使得,红黑树的平均效率更高(不使用AVL树的原因);

并且由于map和set底层都使用的是RBTree,但是map和set分别是KV模型和K模型,它们底层的数据是不同的,所以为了让红黑树的代码可以同时适配map与set,红黑树的实现是有细节的,这也是STL大佬们的厉害之处;

想知道红黑树的实现可以看我前面的博客:

红黑树实现及原理-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_75260318/article/details/137633090?spm=1001.2014.3001.5501

 改造后的红黑树:

模拟实现stl容器/模拟实现map与set/RBTree.hpp · future/my_road - 码云 - 开源中国 (gitee.com)

话不多说我们首先将我们map与set的实现代码拿出来:

由于使用了红黑树作为底层所以其实map和set自己实现的代码量很少都只是调用RBTree的接口而已;

map

template <class K, class V>
class myMap
{struct mapKofT{const K& operator ()(const pair<const K, V>& t){return t.first;}};
public:typedef pair<const K, V> T;typedef typename RBTree<const K, T, mapKofT>::iterator iterator;typedef typename RBTree<const K, T, mapKofT>::const_iterator const_iterator;pair<iterator, bool>insert(const T& data){return _tree.insert(data);}void print(){_tree.print();}iterator begin(){return _tree.begin();}iterator end(){return _tree.end();}const_iterator begin()const{return _tree.begin();}const_iterator end()const{return _tree.end();}V& operator [](const K& key){pair<iterator, bool> ret_insert = _tree.insert(make_pair(key, V()));return ret_insert.first->second;}private:RBTree<const K, T, mapKofT> _tree;
};

 set

template <class K>
class mySet
{struct setKofT{const K& operator ()(const K& t){return t;}};
public:typedef typename RBTree<K, K, setKofT>::const_iterator iterator;typedef typename RBTree<K, K, setKofT>::const_iterator const_iterator;pair<iterator, bool>insert(const K& data){return _tree.insert(data);}void print(){_tree.print();}iterator begin(){return _tree.begin();}iterator end(){return _tree.end();}const_iterator begin()const{return _tree.begin();}const_iterator end()const{return _tree.end();}private:RBTree<K, K, setKofT> _tree;
};

讲解

下面我们先来说说map和set它们做了什么,我们可以看到set和map的成员变量都是_tree但tree传的的模板参数确是不同的;

模板参数

map

template <class K, class V>  map模板参数typedef pair<const K, V> T;  map中typedef的TRBTree<const K, T, mapKofT> _tree;  map的成员变量

set

template <class K>   set模板参数RBTree<K, K, setKofT> _tree;   set成员变量

可以看到我们set传递给RBTree的是两个K类型,而这个K类型又是set需要存储数据的类型,我们再看map,map也一样它传递给RBTree的是K和pair<K,V>类型,其中的pair<K,V>可以代表map存储的数据类型;为什么要这么传递类型呢?当然是为了使得RBTree可以同时适配map和set两种容器啦

追本溯源我们去看底层的RBTree的实现;

RBTree的底层实现是这样的:

template <class T>   红黑树的节点
struct RBTreeNode
{typedef RBTreeNode<T> node;node* _left;node* _right;T _data;node* _parent;Color _cor;RBTreeNode<T>(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _cor(Red), _data(data){}
};template<class K, class T, class KofT>   红黑树的接收的模板参数
class RBTree
{typedef RBTreeNode<T> node;  typedef红黑树的节点
};

 可以看到红黑树底层统一是使用第二个模板参数T来接收map和set的数据的,因为map是KV模型是具有两个类型的,而一个参数无法传递两个类型,如果我们要传递两个参数的话,我们的set又是K模型,只有一个参数类型,所以为了使得RBTree既可以接收map数据类型又可以接收set数据类型,我们使得map传递的T参数是pair类型,这样我们就可以让红黑树可以使用一个模板参数来同时接收map和set的数据了;这样我们的RBTree的节点也可以这样形成了;

现在我们知道了map与set这样传模板参数的原因,这个KofT仿函数吧;

KofT仿函数

mapKofT

struct mapKofT{const K& operator ()(const pair<const K, V>& t){return t.first;}};

setKofT

	struct setKofT{const K& operator ()(const K& t){return t;}};

我们可以看到KofT顾名思义就是在类型T中存储的我们的K类型,在我们的map中T是pair<K,V>类型的,K被包含在了T中,所以我们可以通过这个仿函数来获得map存储的第一个数据;而set中,数据类型T其实就是是K,所以KofT对于set来说就是返回T自己这个类型而已;这里我们可以看作是set为map做出的让步;

那map获得这个第一个数据有什么用呢?

我们把目光放到插入数据的接口处,在我们插入数据时,是不是每次都需要比较数据的大小之后才会插入数据;而比较数据set可以直接使用T来比较,但是map不可以,map的T是pair类型,map想用来比较的是第一个数据K所以,我们不能使用T来直接比较;我们得需要使用仿函数KofT来获得这第一个参数来比较,set底层的KofT返回的就是T所以不影响比较;

insert中寻找位置时的代码node* cur = _root;
node* parent = cur;
KofT koft;while (cur)    
{if (koft(data) < koft(cur->_data))  我们这里就是使用koft来进行比较的{parent = cur;cur = cur->_left;}else if (koft(data) > koft(cur->_data)){parent = cur;cur = cur->_right;}else{return make_pair(iterator(cur), false);}
}

这也就是KofT的作用了;

compare仿函数

其实RBTree在stl的底层实现上是有四个模板参数的

template<class K, class T, class KofT, class compare>

这个仿函数是用来控制比较的方式的,这样我们的排序顺序就会随着仿函数的变化而改变;一样的我们将compare加在insert接口的比较处就好了;

if(koft(data) < koft(cur->_data))改变为compare(koft(data) , koft(cur->_data))

这样就可以控制我们的插入方式,使得大的数据插入到树的左枝小的插入到右枝;

iterator迭代器

我们map与set的迭代器是调用了RBTree的接口的;

注意:我们这里调用其他类模板中的内置类型的时候需要加上typename,因为此时我们的编译器是无法识别类型和静态变量的;

map

	typedef typename RBTree<const K, T, mapKofT>::iterator iterator;typedef typename RBTree<const K, T, mapKofT>::const_iterator const_iterator;

set

	typedef typename RBTree<K, K, setKofT>::const_iterator iterator;typedef typename RBTree<K, K, setKofT>::const_iterator const_iterator;

由于map与set的key值都不允许修改;所以在迭代器传参和使用上有着一些处理;map中因为key值不允许修改,所以传递给迭代器的变量都是const类型的,而set的处理就是const迭代器和普通迭代器都是const迭代器,这样set的数据都是不允许通过迭代器来修改的了;

我们现在来看迭代器的实现;

template<class T, class Ref, class Ptr>//迭代器
struct RBTree_iterator
{typedef RBTree_iterator<T, Ref, Ptr> self;typedef RBTree_iterator<T, T&, T*>iterator;//普通迭代器类型typedef RBTreeNode<T> node;RBTree_iterator (node* newnode):_node(newnode){}//这里的iterator是普通迭代器类型RBTree_iterator (const iterator& it)//当传过来的是普通迭代器的时候是拷贝构造(支持普通迭代器转换为const迭代器): _node(it._node)			    //穿过来的是const迭代器的时候是编译器自动形成的构造函数来构造的			{}self &operator ++(){if (_node == nullptr)return *this;node* subr = _node->_right;if (subr)//如果右节点不为空{while (subr->_left)//找最左节点{subr = subr->_left;}_node = subr;}else//如果右节点为空{node* parent = _node->_parent;while (parent&& _node == parent->_right)//我要是我父亲的左节点才退出{_node = _node->_parent;parent = parent->_parent;}_node = parent;//这个父亲就是我们需要的位置}return *this;}bool operator !=(const self& it){return _node != it._node;}Ptr operator ->(){return &_node->_data;}Ref operator *(){return _node->_data;}node* _node;
};template<class K, class T, class KofT>
class RBTree
{红黑树中定义的迭代器typedef RBTree_iterator<T, T&, T*> iterator;typedef RBTree_iterator<T, const T&,const T*> const_iterator;
}

我们之前早在list的容器那就实现过const迭代器与非const迭代器的的实现,所以这里迭代器的具体实现我们不做过多解释,如果不理解可以看我们前面的博客:

C++stl_list 链表-CSDN博客

operator++

我们只解释一下++代码的实现++代码中,我们迭代器所在节点的下一节点要么是我们右边的最左节点,要么就是我们是父亲节点左节点的父亲,这样的++方式即可满足++,看代码实现辅助理解;

接下来我们看红黑树中对迭代器接口的实现:

iterator begin()
{if (_root == nullptr)return nullptr;node* cur = _root;while (cur->_left){cur = cur->_left;}return iterator(cur);
}iterator end()
{return iterator(nullptr);
}const_iterator begin()const
{if (_root == nullptr)return nullptr;node* cur = _root;while (cur->_left){cur = cur->_left;}return iterator(cur);
}const_iterator end()const
{return iterator(nullptr);
}

可以看到我们的begin()接口获得的就是我们树的最左节点,end()接口获得的就是最右节点的下一个节点(空节点);

在我们实现了这些之后,我们还需要注意的地方就是,我们set中获得的RBTree中的迭代器都是const_iterator,但是可是普通的set的set的成员变量RBTree中的iterator是普通迭代器;所以会发生这样的问题:

 我们现在来看iterator是如何操作的:

template<class T, class Ref, class Ptr>//迭代器
struct RBTree_iterator
{typedef RBTree_iterator<T, Ref, Ptr> self;typedef RBTree_iterator<T, T&, T*>iterator;//普通迭代器类型typedef RBTreeNode<T> node;RBTree_iterator (node* newnode):_node(newnode){}//这里的iterator是普通迭代器类型RBTree_iterator (const iterator& it)//当传过来的是普通迭代器的时候是拷贝构造(支持普通迭代器转换为const迭代器): _node(it._node)			    //穿过来的是const迭代器的时候是编译器自动形成的构造函数来构造的			{}

在迭代器中新增了一个支持接收普通迭代器的构造函数,使得我们在迭代器是const迭代器的时候;也可以接收普通迭代器变量作为参数来构造我们的迭代器;

map中的operator[]

V& operator [](const K& key)
{pair<iterator, bool> ret_insert = _tree.insert(make_pair(key, V()));return ret_insert.first->second;
}

我们红黑树中的insert返回值不再是bool而是一个pair<iterator,bool>类型;这个返回值的作用在前面的使用就已经说过了,所以这里就不过多讲解了;

以上就是大致的map和set的内容;

如果需要查看完整的代码可以到这个链接处查看:

模拟实现stl容器/模拟实现map与set · future/my_road - 码云 - 开源中国 (gitee.com)

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

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

相关文章

安装gensim报错

安装gensim pip install --upgrade gensim装完以后注意一个 装了一堆库其实&#xff0c;看下对应的scipy版本是1.13.0 然后运行 import gensim报错&#xff1a; cannot import name ‘triu’ from ‘scipy.linalg’ https://www.soinside.com/question/brZ46N5EH7bk9xdVwXa…

华为OD机试 - 内存冷热标记(Java 2024 C卷 100分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

Springboot+Vue项目-基于Java+MySQL的高校心理教育辅导系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

【Linux网络编程】TCP协议

TCP协议 1.TCP协议段格式4位首位长度序号和确认序号16位窗口大小6个标志位 2.确认应答机制3.超时重传机制4.连接管理机制如何理解连接如何理解三次握手如何理解四次挥手 5.流量控制6.滑动窗口7.拥塞控制8.延迟应答9.捎带应答10.面向字节流11.粘包问题12.TCP异常情况13.TCP小结1…

亚马逊全球跨境市场地位稳固,武汉星起航深化服务助力合作伙伴

在全球跨境电商市场的激烈竞争中&#xff0c;亚马逊凭借其强大的市场地位和卓越的平台特点&#xff0c;持续引领着行业的发展潮流。作为行业的领军者&#xff0c;亚马逊不仅占据了全球市场的一大部分&#xff0c;还以其深厚的品牌影响力、广泛的覆盖范围和高效的物流运作&#…

雅虎、乐天、煤炉、国际站、newegg、wish等跨境卖家如何提升店铺销量?深度解析自养号测评的价值

一、如何提升销量&#xff1f; 优化产品描述和图片&#xff1a;确保您的产品描述准确、详细&#xff0c;图片清晰、美观。这将有助于提高产品的吸引力&#xff0c;增加潜在买家的购买意愿。 提供良好的客户服务&#xff1a;及时回复买家的咨询和问题&#xff0c;解决他们的疑…

【MySQL】索引篇

SueWakeup 个人主页&#xff1a;SueWakeup 系列专栏&#xff1a;学习技术栈 个性签名&#xff1a;保留赤子之心也许是种幸运吧 本文封面由 凯楠&#x1f4f8;友情提供 目录 本系列传送门 1. 什么是索引 2. 索引的特性 3. 索引的分类 4. 索引的优点及缺点 优点 缺点 5.…

我的电脑图标没了怎么办?恢复图标只需一分钟!

“我打开电脑后&#xff0c;突然发现我的电脑图标没了&#xff0c;这是为什么呢&#xff1f;有什么方法可以找回我的电脑图标吗&#xff1f;” 在日常使用电脑的过程中&#xff0c;电脑桌面的各个图标是保证我们能正常打开应用的前提。我的电脑图标没了怎么办&#xff1f;这可能…

zabbix升级后图形文字不显示

原版本升级后版本6.4.76.4.13 问题现象 更新小版本后zabbix数据图形都有&#xff0c;只有下方文字不显示 处理方式 下载win字体&#xff0c;根据自己选择&#xff0c;上传至/usr/share/zabbix/assets/fonts目录下&#xff0c;修改文件名为jianti.ttf 修改默认字体配置文件…

CSS导读 (元素显示模式 上)

&#xff08;大家好&#xff0c;今天我们将继续来学习CSS的相关知识&#xff0c;大家可以在评论区进行互动答疑哦~加油&#xff01;&#x1f495;&#xff09; 目录 三、CSS的元素显示模式 3.1 什么是元素显示模式 3.2 块元素 3.3 行内元素 3.4 行内块元素 3.5 元素…

HashMap扩容原理(带源码分析)

HashMap的扩容原理 1.扩容流程图 注&#xff1a;拆分链表的规则 这里拆分链表时的一个比较&#xff1a;e.hash & oldCap 0 意思是&#xff1a;某一个节点的hash值和老数组容量求&运算。如果等于0&#xff0c;当前元素在老数组中的位置就是在新数组中的位置。如果不等…

CORS 跨域问题解决预检(OPTIONS)请求解释

场景&#xff1a; 业务要求从把系统B嵌入到系统A中&#xff0c;系统A和系统B是完成不同的两个域名&#xff0c;前端同事完成系统嵌入后&#xff0c;从A系统内部调用B系统的接口时候发现跨域错误&#xff08;CORS error&#xff09;&#xff0c;如下&#xff1a; 什么是跨域&…

vscode开发 vue3+ts 的 uni-app 微信小程序项目

创建uni-app项目&#xff1a; # 创建用ts开发的uni-app npx degit dcloudio/uni-preset-vue#vite-ts 项目名称 # 创建用js开发的uni-app npx degit dcloudio/uni-preset-vue#vite 项目名称VS Code 配置 为什么选择 VS Code &#xff1f; HbuilderX 对 TS 类型支持暂不完善VS…

(学习日记)2024.04.16:UCOSIII第四十四节:内存管理

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

手机数据恢复工具

下载地址&#xff1a;手机数据恢复工具.zip Android/HarmonyOS 文件误删是日常使用电子设备时经常遇到的问题&#xff0c;也许一不小心就就可能会误删。 俗话说&#xff1a;数据无价&#xff0c;一但想要找回一些被删除的文件&#xff0c;就需要耗费大量的精力和财力来恢复文…

【宝德PI300T G2智能小站开发教程(三)】centos配置系统开发源

目录 一.前言 二.配置dns服务器 三.测试连通性 四.设置更新源文件 一.前言 openeular系统的宝德板子没有更新的国内源,要如何配置? 二.配置dns服务器 vi /etc/resolv.conf 添加如下内容: nameserver 8.8.8.8 nameserver 114.114.114.114 三.测试连通性 ping www.ba…

vscode远程连接centos

文章目录 vacode连接linux1. 安装插件2. 查看配置3. 打开ssh4. 远程连接 vacode连接linux 1. 安装插件 在扩展栏搜索remote &#xff0c;找到Remote Development插件&#xff0c;进行安装&#xff1a; 2. 查看配置 打开自己的linux终端&#xff0c;输入ifconfig&#xff0c;…

Laravel/Lumen 中使用 Echo + Socket.IO-Client 实现网页即时通讯广播

此处以 Lumen 9 框架为例说明如何调试通过 Echo 服务端以及客户端 如果你是 Laravel/Lumen 10.47 用户&#xff0c;可以先了解官方的 Laravel Reverb。注意 Laravel Reverb 仅支持 Laravel/Lumen 10.47 以及 PHP 8.2Laravel Reverb 参考官网&#xff1a;https://laravel.com/d…

CSS导读 (复合选择器 下)

&#xff08;大家好&#xff0c;今天我们将继续来学习CSS的相关知识&#xff0c;大家可以在评论区进行互动答疑哦~加油&#xff01;&#x1f495;&#xff09; 目录 2.5 伪类选择器 2.6 链接伪类选择器 2.6.1 链接伪类注意事项 2.6.2 链接伪类选择器实际开发中的写法 2.7 …

智慧医疗app

智慧医疗app是一套融合物联网、云计算和大数据等技术&#xff0c;以患者数据为中心的医疗服务模式&#xff0c;致力于为患者提供更加便捷、高效的医疗服务。 在线挂号、在线问诊、电子病历记录、健康管理以及药品购买等。患者可以通过app选择医生和挂号时间&#xff0c;并在线…