在上一篇博客带你深入理解STL之RBTree中,讲到了STL中关于红黑树的实现,理解起来比较复杂,正所谓前人种树,后人乘凉,RBTree把树都种好了,接下来就该set和map这类关联式容器来“乘凉”了。

STL的set和map都是基于红黑树实现的,和stack和queue都是基于deque一样,它们仅仅是调用了RBTree提供的接口函数,然后进行外层封装即可。本篇博客理解起来比较轻松,set和map的源代码也不多,大家可以慢慢“品味”。另外,还会介绍multiset和multimap这两个容器,并给出它们的区别和应用等。还等什么呢?走吧,带你理解理解set和map吧!

set

set是一种关联式容器,其特性如下:

  • set以RBTree作为底层容器
  • 所得元素的只有key没有value,value就是key
  • 不允许出现键值重复
  • 所有的元素都会被自动排序
  • 不能通过迭代器来改变set的值,因为set的值就是键

针对这五点来说,前四点都不用再多作说明,第五点需要做一下说明。如果set中允许修改键值的话,那么首先需要删除该键,然后调节平衡,在插入修改后的键值,再调节平衡,如此一来,严重破坏了set的结构,导致iterator失效,不知道应该指向之前的位置,还是指向改变后的位置。所以STL中将set的迭代器设置成const,不允许修改迭代器的值。

set的数据结构

// 比较器默认采用less,内部按照升序排列,配置器默认采用alloc
template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set
{
public:
  //  在set中key就是value, value同时也是key
  typedef Key key_type;
  typedef Key value_type;

  // 用于比较的函数
  typedef Compare key_compare;
  typedef Compare value_compare;

private:
  // 内部采用RBTree作为底层容器
  typedef rb_tree<key_type, value_type,
                  identity<value_type>, key_compare, Alloc> rep_type;
  rep_type t;   // t为内部RBTree容器

public:
  // 用于提供iterator_traits<I>支持
  typedef typename rep_type::const_pointer pointer;
  typedef typename rep_type::const_pointer const_pointer;
  typedef typename rep_type::const_reference reference;
  typedef typename rep_type::const_reference const_reference;
  typedef typename rep_type::difference_type difference_type;
  // 设置成const迭代器,set的键值不允许修改
  typedef typename rep_type::const_iterator iterator;
  typedef typename rep_type::const_iterator const_iterator;
  // 反向迭代器
  typedef typename rep_type::const_reverse_iterator reverse_iterator;
  typedef typename rep_type::const_reverse_iterator const_reverse_iterator;
  typedef typename rep_type::size_type size_type;

  iterator begin() const { return t.begin(); }
  iterator end() const { return t.end(); }
  reverse_iterator rbegin() const { return t.rbegin(); }
  reverse_iterator rend() const { return t.rend(); }
  bool empty() const { return t.empty(); }
  size_type size() const { return t.size(); }
  size_type max_size() const { return t.max_size(); }

  // 返回用于key比较的函数
  key_compare key_comp() const { return t.key_comp(); }
  // 由于set的性质, value比较和key使用同一个比较函数
  value_compare value_comp() const { return t.key_comp(); }

  // 声明了两个友元函数,重载了==和<操作符
  friend bool operator== __STL_NULL_TMPL_ARGS (const set&, const set&);
  friend bool operator< __STL_NULL_TMPL_ARGS (const set&, const set&);
  // ...
}

set的构造函数

set提供了如下几个构造函数用于初始化一个set

// 注:下面相关函数都在set类中定义,为了介绍方便才抽出来单独讲解
// 空构造函数,初始化一个空的set
set() : t(Compare()) {}
// 支持自定义比较器,如set<int,greater<int> > myset的初始化
explicit set(const Compare& comp) : t(comp) {}
// 实现诸如set<int> myset(anotherset.begin(),anotherset.end())这样的初始化
template <class InputIterator>
set(InputIterator first, InputIterator last)
: t(Compare()) { t.insert_unique(first, last); }
// 支持自定义比较器的初始化操作
template <class InputIterator>
set(InputIterator first, InputIterator last, const Compare& comp)
: t(comp) { t.insert_unique(first, last); }
// 以另一个set来初始化
set(const set<Key, Compare, Alloc>& x) : t(x.t) {}
// 赋值运算符函数
set<Key, Compare, Alloc>& operator=(const set<Key, Compare, Alloc>& x)
{
    t = x.t;
    return *this;
}

set的操作函数

insert

插入函数,调用RBTree的插入函数即可

typedef  pair<iterator, bool> pair_iterator_bool;
// 由于set不允许键值重复,所以必须调用RBTree的insert_unique函数
// second表示插入操作是否成功
pair<iterator,bool> insert(const value_type& x)
{
  pair<typename rep_type::iterator, bool> p = t.insert_unique(x);
  return pair<iterator, bool>(p.first, p.second);
}

// 在position处插入元素, 但是position仅仅是个提示, 如果给出的位置不能进行插入,
// STL会进行查找, 这会导致很差的效率
iterator insert(iterator position, const value_type& x)
{
  typedef typename rep_type::iterator rep_iterator;
  return t.insert_unique((rep_iterator&)position, x);
}
// 将[first,last)区间内的元素插入到set中
template <class InputIterator>
void insert(InputIterator first, InputIterator last)
{
  t.insert_unique(first, last);
}

erase

擦除函数,用于擦除单个元素或者区间内的元素,直接调用RBTree的函数即可

// 擦除指定位置的元素, 会导致内部的红黑树重新排列
void erase(iterator position)
{
  typedef typename rep_type::iterator rep_iterator;
  t.erase((rep_iterator&)position);
}

// 会返回擦除元素的个数, 其实就是标识set内原来是否有指定的元素
size_type erase(const key_type& x)
{
  return t.erase(x);
}

// 擦除指定区间的元素, 会导致红黑树有较大变化
void erase(iterator first, iterator last)
{
  typedef typename rep_type::iterator rep_iterator;
  t.erase((rep_iterator&)first, (rep_iterator&)last);
}

clean

清除整个set容器,直接调用RBTree的clean函数即可

  void clear() { t.clear(); }

find

查找函数,RBTree也提供了,直接调用即可

  // 查找指定的元素
  iterator find(const key_type& x) const { return t.find(x); }

count

查找制定元素的个数

  // 返回指定元素的个数, set不允许键值重复,其实就是测试元素是否在set中
  size_type count(const key_type& x) const { return t.count(x); }

重载操作符

set重载了==和<操作符,基本上都是调用RBTree的接口函数即可,如下所示:

template <class Key, class Compare, class Alloc>
inline bool operator==(const set<Key, Compare, Alloc>& x,
                       const set<Key, Compare, Alloc>& y) {
  return x.t == y.t;
}

template <class Key, class Compare, class Alloc>
inline bool operator<(const set<Key, Compare, Alloc>& x,
                      const set<Key, Compare, Alloc>& y) {
  return x.t < y.t;
}

其他操作函数

// 返回小于当前元素的第一个可插入的位置
iterator lower_bound(const key_type& x) const
{
  return t.lower_bound(x);
}

// 返回大于当前元素的第一个可插入的位置
iterator upper_bound(const key_type& x) const
{
  return t.upper_bound(x);
}
// 返回与指定键值相等的元素区间
pair<iterator,iterator> equal_range(const key_type& x) const
{
  return t.equal_range(x);
}

multiset

multiset相对于set来说,区别就是multiset允许键值重复,在multiset中调用的是RBTree的insert_equal函数,其他的基本与set相同。

其他的就不赘述了,下面列举一下跟set不同的地方:

// 初始化函数,
// 注意!!!!插入操作采用的是RBTree的insert_equal,而不是insert_unique
template <class InputIterator>
multiset(InputIterator first, InputIterator last)
  : t(Compare()) { t.insert_equal(first, last); }
template <class InputIterator>
multiset(InputIterator first, InputIterator last, const Compare& comp)
  : t(comp) { t.insert_equal(first, last); }

// 插入元素, 注意, 插入的元素key允许重复
iterator insert(const value_type& x)
{
  return t.insert_equal(x);
}

// 在position处插入元素, 但是position仅仅是个提示, 如果给出的位置不能进行插入,
// STL会进行查找, 这会导致很差的效率
iterator insert(iterator position, const value_type& x)
{
  typedef typename rep_type::iterator rep_iterator;
  return t.insert_equal((rep_iterator&)position, x);
}

map

map和set一样是关联式容器,它们的底层容器都是红黑树,区别就在于map的值不作为键,键和值是分开的。它的特性如下:

  • map以RBTree作为底层容器
  • 所有元素都是键+值存在
  • 不允许键重复
  • 所有元素是通过键进行自动排序的
  • map的键是不能修改的,但是其键对应的值是可以修改的

在map中,一个键对应一个值,其中键不允许重复,不允许修改,但是键对应的值是可以修改的,原因可以看上面set中的解释。下面就一起来看看STL中的map的源代码。

map的数据结构

// 默认比较器为less<key>,元素按照键的大小升序排列
template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class map {
public:
  typedef Key key_type;                         // key类型
  typedef T data_type;                          // value类型
  typedef T mapped_type;
  typedef pair<const Key, T> value_type;        // 元素类型, 要保证key不被修改
  typedef Compare key_compare;                  // 用于key比较的函数
private:
  // 内部采用RBTree作为底层容器
  typedef rb_tree<key_type, value_type,
                  identity<value_type>, key_compare, Alloc> rep_type;
  rep_type t; // t为内部RBTree容器
public:
  // 用于提供iterator_traits<I>支持
  typedef typename rep_type::const_pointer pointer;
  typedef typename rep_type::const_pointer const_pointer;
  typedef typename rep_type::const_reference reference;
  typedef typename rep_type::const_reference const_reference;
  typedef typename rep_type::difference_type difference_type;
  // 注意:这里与set不一样,map的迭代器是可以修改的
  typedef typename rep_type::iterator iterator;
  typedef typename rep_type::const_iterator const_iterator;
  // 反向迭代器
  typedef typename rep_type::const_reverse_iterator reverse_iterator;
  typedef typename rep_type::const_reverse_iterator const_reverse_iterator;
  typedef typename rep_type::size_type size_type;

  // 常规的返回迭代器函数
  iterator begin() { return t.begin(); }
  const_iterator begin() const { return t.begin(); }
  iterator end() { return t.end(); }
  const_iterator end() const { return t.end(); }
  reverse_iterator rbegin() { return t.rbegin(); }
  const_reverse_iterator rbegin() const { return t.rbegin(); }
  reverse_iterator rend() { return t.rend(); }
  const_reverse_iterator rend() const { return t.rend(); }
  bool empty() const { return t.empty(); }
  size_type size() const { return t.size(); }
  size_type max_size() const { return t.max_size(); }

  // 返回用于key比较的函数
  key_compare key_comp() const { return t.key_comp(); }

  // 由于map的性质, value和key使用同一个比较函数, 实际上我们并不使用value比较函数
  value_compare value_comp() const { return value_compare(t.key_comp()); }

  // 注意: 这里有一个常见的陷阱, 如果访问的key不存在, 会新建立一个
  T& operator[](const key_type& k)
  {
    return (*((insert(value_type(k, T()))).first)).second;
  }
  // 重载了==和<操作符,后面会有实现
  friend bool operator== __STL_NULL_TMPL_ARGS (const map&, const map&);
  friend bool operator< __STL_NULL_TMPL_ARGS (const map&, const map&);
}

map的构造函数

map提供了一下的构造函数来初始化一个map

// 空构造函数,直接调用RBTree的空构造函数
map() : t(Compare()) {}
explicit map(const Compare& comp) : t(comp) {}
// 提供类似map<int,int> myMap(anotherMap.begin(),anotherMap.end())的初始化
template <class InputIterator>
map(InputIterator first, InputIterator last)
  : t(Compare()) { t.insert_unique(first, last); }
// 提供类似map<int,int> myMap(anotherMap.begin(),anotherMap.end(),less<int>)初始化
template <class InputIterator>
map(InputIterator first, InputIterator last, const Compare& comp)
  : t(comp) { t.insert_unique(first, last); }
// 提供类似map<int> maMap(anotherMap)的初始化
map(const map<Key, T, Compare, Alloc>& x) : t(x.t) {}
// 重载=操作符,赋值运算符
map<Key, T, Compare, Alloc>& operator=(const map<Key, T, Compare, Alloc>& x)
{
  t = x.t;
  return *this;
}

map的操作函数

insert

同set一样,直接调用RBTree的插入函数即可,注意map不允许键值重复,所以调用的是insert_unique

// 对于相同的key, 只允许出现一次, bool标识
pair<iterator,bool> insert(const value_type& x) { return t.insert_unique(x); }

// 在position处
插入元素, 但是position仅仅是个提示, 如果给出的位置不能进行插入,
// STL会进行查找, 这会导致很差的效率
iterator insert(iterator position, const value_type& x)
{
  return t.insert_unique(position, x);
}
// 将[first,last)区间内的元素插入到map中
template <class InputIterator>
void insert(InputIterator first, InputIterator last) {
  t.insert_unique(first, last);
}

erase

同set,直接调用即可

// 擦除指定位置的元素, 会导致内部的红黑树重新排列
void erase(iterator position) { t.erase(position); }

// 会返回擦除元素的个数, 其实就是标识map内原来是否有指定的元素
size_type erase(const key_type& x) { return t.erase(x); }
void erase(iterator first, iterator last) { t.erase(first, last); }

clean

同set,直接调用即可

void clear() { t.clear(); }

find

// 查找指定key的元素
iterator find(const key_type& x) { return t.find(x); }
const_iterator find(const key_type& x) const { return t.find(x); }
````

<div class="se-preview-section-delimiter"></div>

### 重载运算符
上面介绍到map重载了[],==和<运算符,[]的实现已经介绍过,下面是==和<的实现

<div class="se-preview-section-delimiter"></div>

```cpp
// 比较map直接是对其底层容器t的比较,直接调用RBTree的比较函数即可
template <class Key, class T, class Compare, class Alloc>
inline bool operator==(const map<Key, T, Compare, Alloc>& x,
                       const map<Key, T, Compare, Alloc>& y)
{
  return x.t == y.t;
}

template <class Key, class T, class Compare, class Alloc>
inline bool operator<(const map<Key, T, Compare, Alloc>& x,
                      const map<Key, T, Compare, Alloc>& y)
{
  return x.t < y.t;
}

其他操作函数

// 返回小于当前元素的第一个可插入的位置
iterator lower_bound(const key_type& x) {return t.lower_bound(x); }
const_iterator lower_bound(const key_type& x) const
{
  return t.lower_bound(x);
}

// 返回大于当前元素的第一个可插入的位置
iterator upper_bound(const key_type& x) {return t.upper_bound(x); }
const_iterator upper_bound(const key_type& x) const
{
  return t.upper_bound(x);
}
// 返回与指定键值相等的元素区间
pair<iterator,iterator> equal_range(const key_type& x)
{
  return t.equal_range(x);
}

multimap

multimap和map的关系就跟multiset和set的关系一样,multimap允许键的值相同,因此在插入操作的时候用到insert_equal(),除此之外,基本上与map相同。

下面就仅仅列出不同的地方

template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class multimap
{
  // ... 其他地方与map相同
  // 注意下面这些函数都调用的是insert_equal,而不是insert_unique
  template <class InputIterator>
  multimap(InputIterator first, InputIterator last)
    : t(Compare()) { t.insert_equal(first, last); }

  template <class InputIterator>
  multimap(InputIterator first, InputIterator last, const Compare& comp)
    : t(comp) { t.insert_equal(first, last); }

  // 插入元素, 注意, 插入的元素key允许重复
  iterator insert(const value_type& x) { return t.insert_equal(x); }

  // 在position处插入元素, 但是position仅仅是个提示, 如果给出的位置不能进行插入,
  // STL会进行查找, 这会导致很差的效率
  iterator insert(iterator position, const value_type& x)
  {
    return t.insert_equal(position, x);
  }
  // 插入一个区间内的元素
  template <class InputIterator>
  void insert(InputIterator first, InputIterator last)
  {
    t.insert_equal(first, last);
  }
  // ...其余地方和map相同
}

总结

总的来说,这四类容器仅仅只是在RBTree上进行了一层封装,首先,set和map的区别就在于键和值是否相同,set中将值作为键,支持STL的提供的一些交集、并集和差集等运算;map的键和值不同,每个键都有自己的值,键不能重复,但是值可以重复。

multimap和multiset就在map和set的基础上,使他们的键可以重复,除此之外基本等同。

关于map和set的用法,大家可以在其源代码中找到,这里就不在赘述,本系列博客也是基于源码的角度来分析这些容器。

欢迎大家关注我的个人博客,ZeeCoder’s Blog

这篇博客到此,end!

带你深入理解STL之Set和Map的更多相关文章

  1. 带你深入理解STL之Stack和Queue

    上一篇博客,带你深入理解STL之Deque容器中详细介绍了deque容器的源码实现方式.结合前面介绍的两个容器vector和list,在使用的过程中,我们确实要知道在什么情况下需要选择恰当的容器来满足 ...

  2. 带你深入理解STL之Vector容器

    C++内置了数组的类型,在使用数组的时候,必须指定数组的长度,一旦配置了就不能改变了,通常我们的做法是:尽量配置一个大的空间,以免不够用,这样做的缺点是比较浪费空间,预估空间不当会引起很多不便. ST ...

  3. 带你深入理解STL之迭代器和Traits技法

    在开始讲迭代器之前,先列举几个例子,由浅入深的来理解一下为什么要设计迭代器. //对于int类的求和函数 int sum(int *a , int n) { int sum = 0 ; for (in ...

  4. 带你深入理解STL之RBTree

    最近一直忙于校招的笔试,STL的深入理解系列也耽搁了好几天,再加上!红黑树真的是超级超级难理解,超级超级复杂,参考了好多博客上的大神的理解才稍微明白一点,勉强入个门,下面请以一个菜鸟的角度跟着我一起学 ...

  5. 带你深入理解STL之Deque容器

    在介绍STL的deque的容器之前,我们先来总结一下vector和list的优缺点.vector在内存中是分配一段连续的内存空间进行存储,其迭代器采用原生指针即可,因此其支持随机访问和存储,支持下标操 ...

  6. 带你深入理解STL之List容器

    上一篇博客中介绍的vector和数组类似,它拥有一段连续的内存空间,并且起始地址不变,很好的支持了随机存取,但由于是连续空间,所以在中间进行插入.删除等操作时都造成了内存块的拷贝和移动,另外在内存空间 ...

  7. 带你深入理解STL之空间配置器(思维导图+源码)

    前不久把STL细看了一遍,由于看得太"认真",忘了做笔记,归纳和总结这步漏掉了.于是为了加深印象,打算重看一遍,并记录下来里面的一些实现细节.方便以后能较好的复习它. 以前在项目中 ...

  8. 一文带你深入理解JVM,看完之后你还敢说你懂JVM吗?颠覆you认知

    前言 今天带大家深入理解JVM,从入门到精通,希望大家能够喜欢~~~ 概念 JVM是可运行 Java 代码的假想计算机 ,包括一套字节码指令集.一组寄存器.一个栈.一个垃圾回收,堆 和 一个存储方法域 ...

  9. STL模板中的map的使用与例题

    最近的计分赛,记得自己的都只是过了两题.遇到了两次map,自己在寒假看了一点的map,只知道在字符串匹配的时候可以用的到.但是自己对map的使用还是不够熟练使用,这回在第一次和第二次的计分赛中都遇到可 ...

随机推荐

  1. 【swift】ios中生成二维码

    ios开发中可以自己代码生成二维码,需要使用到一个框架 CoreImage CoreImage框架可以做滤镜,Gif动图,二维码等 先看效果图 下面直接贴上代码(OC也是下面一样的流程) func c ...

  2. php一句话反弹bash shell

    来源:https://www.leavesongs.com/PHP/backshell-via-php.html 用到了,记录一下 $sock = fsockopen($ip, $port); $de ...

  3. [POJ 2248]Addition Chains

    Description An addition chain for n is an integer sequence with the following four properties: a0 = ...

  4. [SDOI2017]新生舞会

    Description 学校组织了一次新生舞会,Cathy作为经验丰富的老学姐,负责为同学们安排舞伴.有n个男生和n个女生参加舞会 买一个男生和一个女生一起跳舞,互为舞伴.Cathy收集了这些同学之间 ...

  5. codeforces 651C Watchmen

    Watchmen are in a danger and Doctor Manhattan together with his friend Daniel Dreiberg should warn t ...

  6. 【GDOI】【图论-最短路】时间与空间之旅

    最近打的一场校内训练的某题原题... 题目如下: Description 公元22××年,宇宙中最普遍的交通工具是spaceship.spaceship的出现使得星系之间的联系变得更为紧密,所以spa ...

  7. (MariaDB/MySQL)之DML(2):数据更新、删除

    本文目录:1.update语句2.delete语句 2.1 单表删除 2.2 多表删除3.truncate table 1.update语句 update用于修改表中记录. # 单表更新语法: UPD ...

  8. C语言第三次程序设计作业

    (一)改错题 计算f(x)的值:输入实数x,计算并输出下列分段函数f(x)的值,输出时保留1位小数. 1)源程序(有错误的程序) #include <stdio.h> int main(v ...

  9. 关闭默认共享,禁止ipc$空连接

    关闭默认共享,禁止ipc$空连接 要防止别人用ipc$和默认共享入侵,需要禁止ipc$空连接,避免入侵者取得用户列表,并取消默认共享 禁止ipc$空连接进行枚举运行regedit,找到如下组键[HKE ...

  10. Oracle 导入、导出DMP(备份)文件

    首先说明dmp文件: Oracle备份文件是以dmp结尾,这种文件是oracle的逻辑备份文件,常用于数据库逻辑备份,数据库迁移等操作. 一.Oracle导入备份文件步骤:我用的是Oracle 11g ...