【C++】set map的使用和源码解析

前言

[!NOTE] 先看前文
set map底层采用的是红黑树作为底层数据结构,因此插入删除查询的时间复杂度都为O(logN),其中红黑树又是一种平衡二叉搜索树,因此要想看得懂本章,需要先了解一下二叉搜索树,AVLTree和红黑树。链接:

  1. 二叉树学习模拟实现
  2. AVLTree学习模拟实现
  3. 红黑树学习模拟实现

set的使用

与序列容器vector不同。set是非序列容器,它在内存里并不是连续的,而是通过指针将一块一块的内存串联起来组成的一个容器。相比于vector,它的内存缓存命中率要低一些。但优点是更低的寻找,插入,删除的时间复杂度。

set底层其实存储的是pair键值对,只不过相比于map的 key - value,set的key本身就等于value。因此,set的插入只需要插入value,查询也只需要value,同时set元素不可以重复,且set本身是有序的,通过迭代器遍历可以顺序输出。

set的构造

set<T> s;

set的成员函数

//返回pair,first是迭代器,second是bool值
pair<iterator, bool> insert(const value_type& x);
void erase(iterator position);
void clear();
iterator find(const key_type& x);

map的使用

map中,键值key用来排序和唯一标识元素,而值value中存储与此键值key相关联的内容。键值key和值value的类型可能不同,而且在map内部,key和value通过成员类型 value_type绑定在一起,这里的value_type即是pair

map的构造

map<K, V> m;

map的成员函数

pair<iterator, bool> insert(const value_type& x);
void erase(iterator position);
size_type erase(const key_type& x);
void clear();
iterator find(const key_type& x);
bool empty() const;
size_type size() const;

源码分析

set

请添加图片描述

set的key_type和value_type都是key,用红黑树为底层,大部分操作通过适配红黑树实现


请添加图片描述
set的迭代器都是用的红黑树的const迭代器,所以不能通过迭代器来更改元素


请添加图片描述

这里因为是set,使用了insert_unique,可以通过函数名字猜到其实现,大概就是内部会检测是否存在,若存在则不插入,返回false,不存在才插入返回true。而与该函数对应的是insert_equal(),应该是multiset的插入。


请添加图片描述

可以看到所以的这些都是调用的红黑树的接口


insert / erase

请添加图片描述
请添加图片描述

insert调用红黑树的insert,erase同理


请添加图片描述

set中最常用的find


map

请添加图片描述

map与set最大的不同就是,map的key和value并不相同。其中对value_type的定义中,key依然定义的是const,也就是说我们无法用迭代器取修改key,但是我们可以用迭代器取修改value。

map中的仿函数设计,调用comp函数,而comp函数中定义了具体的比较方式。支持用户自定义Compare类进行排序,只需要自己创建一个Compare类,并在类中重载一遍仿函数即可。

下面的底层依然采用红黑树,其中第三个参数使用select1st,而set使用的是identity,其他并无差别。


请添加图片描述

这里的[]操作符重载,传入一个key,返回一个value的引用。这里涉及到两种情况,分别是:

  1. key已经存在 ->返回一个左值
  2. key不存在 ->返回一个右值
    通过一行代码可以同时实现以上两个功能,首先通过insert插入一个临时的value_type;我们知道insert返回的是一个pair,其中first是红黑树的iterator。而insert插入的key如果存在,会返回目标地址,不存在则找到目标位置插入元素,然后返回插入的元素的地址。通过插入一个临时对象返回其pair,实现了既能接收左值也能接收右值的功能:
  3. 当map[]传入左值时, 返回map找到的位置,返回pair的first指向存在的元素,再通过second取得实值
  4. 当map[]传入右值时,返回可以插入的位置,pair的first指向一个不存在的元素,通过second的将实值赋给返回值引用

insert / erase

请添加图片描述
.
请添加图片描述

删除操作


请添加图片描述


RBTree

请添加图片描述

可以看到,红黑树的基节点除了红和黑采用的是const类型修饰bool变量来区分RED和BLACK,我的模拟实现采用的是枚举类型之外,其他都差不多。


请添加图片描述

可以看到,红黑树的迭代器是一个base_ptr和一个双向迭代器,并在迭代器创建一个node成员变量。而上方的类继承自红黑树节点基类,多加了一个模板类型的链表自定义链表。


请添加图片描述
请添加图片描述

再定义了两个函数,用来做底层的逻辑。通过二叉搜索树的性质,完成对树的迭代器移动操作,用来重载真正的迭代器的++ --操作。


请添加图片描述

最后就是根据上面的基类和基迭代器类,组合起来成为了一个真正的迭代器类。这里面的嵌套逻辑还是比较复杂,其中link_type初始化迭代器,并通过迭代器的++ --,通过指针的形式做到了类似vector那样的连续地址空间的遍历,只是我们在iterator层看不到底层的实现,完成了封装。

后面就是红黑树的实现代码了,原理和上一篇文章讲的差不多,其他的就是填代码,就不放进文章里了,毕竟有1000多行,大家感兴趣去找来看吧,我因为时间关系没办法看的那么细了,等以后有空余时间了,一定回来好好读读~


结束语

set 和 map 的模拟实现也写了一半了,其实主要是对红黑树接口的封装,也比较简单,真正的难点是红黑树的实现,以及迭代器的设计。当然,模拟实现肯定是简化迭代器的设计思考的,明天清明节就结束了,我尽量明天吧set 和 map的模拟实现写完吧,下篇文章再见

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值