前言
[!NOTE] 先看前文
set map底层采用的是红黑树作为底层数据结构,因此插入删除查询的时间复杂度都为O(logN),其中红黑树又是一种平衡二叉搜索树,因此要想看得懂本章,需要先了解一下二叉搜索树,AVLTree和红黑树。链接:
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的引用。这里涉及到两种情况,分别是:
- key已经存在 ->返回一个左值
- key不存在 ->返回一个右值
通过一行代码可以同时实现以上两个功能,首先通过insert
插入一个临时的value_type;我们知道insert返回的是一个pair,其中first是红黑树的iterator。而insert
插入的key如果存在,会返回目标地址,不存在则找到目标位置插入元素,然后返回插入的元素的地址。通过插入一个临时对象返回其pair,实现了既能接收左值也能接收右值的功能: - 当map[]传入左值时, 返回map找到的位置,返回pair的first指向存在的元素,再通过second取得实值
- 当map[]传入右值时,返回可以插入的位置,pair的first指向一个不存在的元素,通过second的将实值赋给返回值引用
insert / erase
.
删除操作
RBTree
可以看到,红黑树的基节点除了红和黑采用的是const类型修饰bool变量来区分RED和BLACK,我的模拟实现采用的是枚举类型之外,其他都差不多。
可以看到,红黑树的迭代器是一个base_ptr和一个双向迭代器,并在迭代器创建一个node成员变量。而上方的类继承自红黑树节点基类,多加了一个模板类型的链表自定义链表。
再定义了两个函数,用来做底层的逻辑。通过二叉搜索树的性质,完成对树的迭代器移动操作,用来重载真正的迭代器的++ --操作。
最后就是根据上面的基类和基迭代器类,组合起来成为了一个真正的迭代器类。这里面的嵌套逻辑还是比较复杂,其中link_type初始化迭代器,并通过迭代器的++ --,通过指针的形式做到了类似vector那样的连续地址空间的遍历,只是我们在iterator层看不到底层的实现,完成了封装。
后面就是红黑树的实现代码了,原理和上一篇文章讲的差不多,其他的就是填代码,就不放进文章里了,毕竟有1000多行,大家感兴趣去找来看吧,我因为时间关系没办法看的那么细了,等以后有空余时间了,一定回来好好读读~
结束语
set 和 map 的模拟实现也写了一半了,其实主要是对红黑树接口的封装,也比较简单,真正的难点是红黑树的实现,以及迭代器的设计。当然,模拟实现肯定是简化迭代器的设计思考的,明天清明节就结束了,我尽量明天吧set 和 map的模拟实现写完吧,下篇文章再见