🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。
🚁 个人主页:不 良
🔥 系列专栏:🛸C++ 🛹Linux
📕 学习格言:博观而约取,厚积而薄发
🌹 欢迎进来的小伙伴,如果小伙伴们在学习的过程中,发现有需要纠正的地方,烦请指正,希望能够与诸君一同成长! 🌹
map和set底层结构都是二叉搜索树。
关联式容器
在前面学过的STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。
关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。本篇介绍的set和map就是关联式容器。
关联式容器中数据和数据之间有非常强的关联关系,不能随意插入。
键值对
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。
比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且英文单词与其中文含义是一一对应的关系,即通过输入单词,在词典中就可以找到与其对应的中文含义。
SGI-STL中关于键值对的定义:
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 seco_type;
T1 first;//key
T2 second;//value
pair():first(T1()),second(T2()){
}//默认构造
pair(const T1& a, const T2& b):first(a),second(b){
}//构造函数
};
在上面的pair类中:
- 是一个模板类,可以存放任意类型的键对值;
- 有两个成员变量:first和second;
一般使用first表示key,second表示value。 键值对要在类外进行访问,所以使用struct定义类而不用class。
make_pair
该函数用来创建pair类型的匿名对象。
根据上面pair类的定义,我们要想创建一个pair类型的匿名对象需要像下面这样写:
pair<string,int>("苹果",1);
上面的方式还需要指定参数类型,所以为了方便,就封装了一个函数模板make_pair,用来快速创建一个匿名对象:
make_pair("苹果",1);
make_pair函数模板的定义如下:

树形结构的关联式容器
根据应用场景的不同,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。
树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。 与树形结构容器不同的是,哈希结构容器中的元素是一个无序的序列。
set
认识set
-
set是按照一定次序存储元素的容器,使用set的迭代器遍历set中的元素,可以得到有序序列(默认是升序);
-
set虽然是采用了键值对的方式存储数据,但是在set中,value就是key,即相当于是K模型,并且每个value必须是唯一的,不可以重复,所以可以使用set进行去重;
-
与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对,因此在set容器中插入元素时,只需要插入value即可,不需要构造键值对;
-
set中的元素不能在容器中修改(元素总是const),因为set在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树。但是可以从容器中插入或删除它们;
-
在内部,set中的元素总是按照其内部比较对象所指示的特定严格弱排序准则进行排序。当不传入内部比较对象时,set中的元素默认按照小于来比较(默认是升序);
-
set容器通过key访问单个元素的速度通常比unordered_set容器慢,但set容器允许根据顺序对元素进行直接迭代;
-
set在底层是用平衡搜索树(红黑树)实现的,所以在set当中查找某个元素的时间复杂度为logN。
set模板参数列表

第一个模板参数T: set中存放元素的类型,实际在底层存储<value, value>的键值对;
Compare(仿函数):set中元素默认按照小于来比较(默认是升序);
Alloc:set中元素空间的管理方式,默认使用STL提供的空间配置器管理。
构造函数

构造函数有三个,分别是默认构造函数、使用迭代器区间的构造函数、拷贝构造函数。
下面用代码来使用上面三个构造函数:
#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{
set<int> s1;//默认构造函数
vector<int> v = {
66,5,4,11,66,66,77,8,9,22,10,99 };
set<int> s2(v.begin(), v.end());//迭代器区间构造
set<int> s3(s2);//拷贝构造
//打印s1
for (auto& e : s1)
{
cout << e << " ";
}
cout << endl;
//s1内容为空
//打印s2
for (auto& e : s2)
{
cout << e << " ";
}
cout << endl;
//s2打印出来为4 5 8 9 10 11 22 66 77 99
//打印s3
for (auto& e : s3)
{
cout << e << " ";
}
cout << endl;
//s3打印出来为4 5 8 9 10 11 22 66 77 99
}
运行结果分析:

根据结果我们可以看到set具有排序的功能(默认是升序),而且在数组中相同的元素经放在set容器之后就没有相同的元素了,说明了set具有去重的功能。set具有去重的功能,其底层是二叉搜索树,不能存放相同的元素。
迭代器
set的输出结果是升序的,其底层是二叉搜索树,打印二叉搜索树时使用中序打印出来的就是升序,所以set的迭代器在++时,移动的顺序就是中序遍历的顺序,所以使用迭代器遍历时得到的结果和二叉搜索树中序遍历得到的结果一致。
set迭代器的使用和之前学过的容器一样:

具体使用示范代码:
#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{
vector<int> v = {
66,5,4,11,66,66,77,8,9,22,10,99 };
set<int> s1(v.begin(), v.end());//迭代器区间构造
//正向迭代器的使用
set<int>::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << " ";
it++;
}
cout << endl;
//反向迭代器的使用
set<int>::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
运行结果分析:

插入(insert)

1、插入一个指定值并且返回一个键值对,返回的键值对中迭代器指向新插入的位置。
返回的是pair类型的对象,其中pair的第一个成员是迭代器,其指向的是插入的元素在set中的位置;第二个成员是bool类型,如果成功插入pair类中的第二个成员为true,插入失败pair类中的第二个成员为false。所以这里insert能够做到两个功能:查找和插入(查找成功返回false,查找失败返回true)。
pair<iterator,bool> insert (const value_type& val);
2、在指定的位置插入数据,并且返回插入的位置:
iterator insert (iterator position, const value_type& val);
3、将一段迭代器区间插入到set对象中:
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
向set中插入一个对象,并且能够保持结构不变,依然符合平衡搜索树。
代码示范:
#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{
vector<int> v = {
66,5,4,11,66,66,77,8,9,22,10,99 };
set<int> s1(v.begin(), v.end());//迭代器区间构造
//正向迭代器的使用
set<int>::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << " ";
it++;
}
cout << endl;
//构造一个pair类型的对象
pair<set<int>::iterator, bool> p;
//接收插入之后的返回值
p = s1.insert(67);
it = s1.begin();
//打印插入的值和是否成功
cout << *(p.first) << ":" << p.second << endl;
p = s1.insert(67);
//打印插入的值和是否成功
cout << *(p.first) << ":" << p.second << endl;
}
打印结果:

指定位置插入有可能会破环二叉搜索树的结构,所以不建议使用指定位置插入。
删除(erase)

删除指定迭代器位置的值:

最低0.47元/天 解锁文章
542

被折叠的 条评论
为什么被折叠?



