一、关联式容器
我们曾经接触过的vector、list、deque等容器都被称之为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。
关联式容器也是用来存储数据的,与序列式容器不同的是,里面存储的是
<key,value>结构的键值对,在数据检索时比序列式容器效率更高。
二、键值对
键值对用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员key和value,key代表键值,value表示与key对应的信息。比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该英文单词,在字典中就可以找到与其对应的中文含义。
SGI-STL中关于键值的定义如下:
template <class T1, class T2>
struct pair
{
typedef T1 frist_type;
typedef T2 second_type;
T1 frist;
T2 second;
pair(): frist(T1()),second(T2())
{}
pair(const T1& a,const T2& b): frist(a),second(b)
{}
};
三、树形结构的关联式容器
根据使用场景的不同,STL总共实现了两种不同结构的关联式容器:树形结构和哈希结构。树形结构的关联式容器主要有四种:map、set、multimap、
multiset。这四个容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结构,容器中的元素是一个有序的序列。
1、set
(1)set的介绍
①set是按照一定次序存储元素的容器。
②在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或者删除它们。
③在内部,set中的元素总是按照其内部比较对象(类型为Compare)所指示的特定严格弱排序准则进行排序。
④set容器通过key访问单个元素的速度通常比unordered_set容器慢,但是它们允许根据子集的顺序对直接迭代子集。
⑤set在底层是用二叉搜索树(红黑树)实现的。
注意:
- 与map/multimap不同,map/multimap中存储的是真正的键值对<key,value>,set中只放value,但是在底层实际存放的是由<value,value>构成的键值对。
- set中插入元素时,只需要插入value即可,不需要构造键值对。
- set中的元素不可以重复(因此可以用set去重)。
- 使用set的迭代器遍历set中的元素,可以得到有序序列。
- set中的元素默认按照小于来比较。
- set中查找某个元素,时间复杂度为log2^n。
- set中的元素不允许修改。
(2)set的使用
①set的模板参数列表
T:set中存放元素的类型,实际底层中存储<value,value>的键值对。
Compare:比较器的类型,缺省情况下按照小于来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)
Alloc:set中元素空间的管理方式,使用STL提供的空间配置管理器。
②set的构造
函数声明 | 功能介绍 |
set(cosnt Compare& comp=Compare(),const Allocator& alloc=Allocator() ); | 构造空的set |
set(InputIterator frist,InputIterator last,const Compare& comp=Compare(),const Allocator& alloc=Allocator() ); | 用(frist,last)迭代器区间的元素构造set |
set(const set<Key,Compare,Allocator>& x); | set的拷贝构造 |
③set的迭代器
函数声明 | 功能介绍 |
返回set中起始位置元素的迭代器 | |
返回set中最后一个元素后面的迭代器 | |
返回set第一个元素的反向迭代器 | |
返回set最后一个元素下一个位置的反向迭代器 | |
返回set中起始位置元素的const迭代器 | |
返回set中最后一个元素后面的const迭代器 | |
返回set第一个元素的反向const迭代器 | |
返回set最后一个元素下一个位置的反向const迭代器 |
set的容量
函数声明 | 功能介绍 |
检测set是否为空,空返回true,否则返回true | |
返回set中有效元素的个数 |
⑤set的修改
函数声明 | 功能介绍 |
在set中插入元素x,实际插入的是<x,x>构成的键值对,如果插入成功,返回<该元素在set中的位置,true>,如果插入失败,说明在set中已经存在,返回<该元素在set中的位置,false> | |
删除set中position位置上的元素 | |
删除set中值为x的元素,返回删除的元素的个数 | |
删除set中[frist,last)区间中的元素 | |
交换set中的元素 | |
将set中的元素清空 | |
返回set中值为x的元素的位置的迭代器,如果没找到,返回end迭代器 | |
返回set中值为x的元素的个数 | |
返回第一个大于等于val值位置的迭代器 | |
返回第一个大于val值位置的迭代器 | |
pair<iterator,iterator> equal_range (const value_type& val) const; | 返回<第一个大于等于val值位置的迭代器,第一个大于val值位置的迭代器> |
⑥补充
- insert的返回类型时pair<iterator,bool>,这是一个模板类,它的第一个参数frist,类型为iterator,另一个参数second类型为bool。
- 可以用count来判断一个值在不在set里。
⑦set的使用举例
void Test1()
{
vector<int> v = { 5,4,2,2,3,5,2,1,8,7,9,10,4 ,6 };//有重复元素
set<int> s(v.begin(), v.end());
for (auto a : s)//打印set里的元素
{
cout << a << " ";
}//打印出来的元素没有重复,并且按升序排列
cout << endl;
set<int>::iterator it = s.find(10);//找到10
if (it != s.end())//如果成功找到了
{
s.erase(it);//删除它
}
it = s.lower_bound(5);//打印大于等于5的值
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
it = s.upper_bound(5);//打印大于5的值
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
2、multiset
(1)multiset的介绍
multiset与set主要的差别就在于它允许插入重复的元素。
(2)multiset的使用
multiset和set的接口基本相同
void Test1()
{
vector<int> v = { 5,4,2,2,3,5,2,1,8,7,9,10,4 ,6 };//有重复元素
multiset<int> s(v.begin(), v.end());
for (auto a : s)//打印set里的元素
{
cout << a << " ";
}//打印出来的元素有重复
cout << endl;
set<int>::iterator it = s.find(10);
//multiset里的find查找的元素如果有重复,只会找中序遍历的第一个元素。
if (it != s.end())//如果成功找到了
{
s.erase(it);//删除它
}
auto ret = s.equal_range(2);
//multiset里的equal_range可以帮助我们删除相同元素
//当然,直接erase(2)效果也是相通的
s.erase(ret.first, ret.second);
for (auto a : s)//打印set里的元素
{
cout << a << " ";
}
cout << endl;
}
3、map
(1)map的介绍
①map是关联式容器,它按照特定的次序(按照key值来比较)存储有键值key和值value组合而成的元素。
②在map中,键值key通常用于排序和唯一地标识元素,而值value中存储与键值key关联的内容。键值key和值value的类型可能不同,并且在map内部,key与value通过成员类型value_type绑定在一起。
typedef pair<const key,T> value_type;
③在内部,map中的元素总是按照键值key进行比较排序的。
④map中通过键值访问单个元素的速度比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代。
⑤map支持下表访问,即在[]中放入key,就可以找到与key对应的value。
⑥map在底层是用二叉搜索树(红黑树)实现的。
(2)map的使用
①map的模板参数说明
Key:键值对中key的类型。
T:键值对中value的类型。
Compare:比较器的类型,map中的元素是按照key来比较的,缺省情况下按照小于来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)
Alloc:通过空间配置器来申请底层空间,不需要用户传递,,除非用户不想使用标准库提供的空间配置器。
②map的构造
函数声明 | 功能介绍 |
map (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()); | 构造一个空的map |
map (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()); | 用(frist,last)迭代器区间的元素构造map |
map (const map& x); | map的拷贝构造 |
③map的迭代器
函数声明 | 功能介绍 |
返回map中起始位置元素的迭代器 | |
返回map中最后一个元素后面的迭代器 | |
返回map第一个元素的反向迭代器 | |
返回map最后一个元素下一个位置的反向迭代器 | |
返回map中起始位置元素的const迭代器 | |
返回map中最后一个元素后面的const迭代器 | |
返回map第一个元素的反向const迭代器 | |
返回map最后一个元素下一个位置的反向const迭代器 |
④map的容量
函数声明 | 功能介绍 |
检测map是否为空,空返回true,否则返回true | |
返回map中有效元素的个数 |
⑤map中元素的修改
函数声明 | 功能简介 |
在map中插入键值对val,注意val是一个键值对,返回值也是键值对:iterator代表新插入元素的位置,bool表示是否插入成功 | |
删除position位置上的元素 | |
删除键值为k的元素 | |
删除set中[frist,last)区间中的元素 | |
交换两个map中的元素 | |
将map中的元素清空 | |
在map中查找key为k的元素,找到返回该元素的位置的迭代器,否则返回end | |
返回map中key为k的元素的个数 | |
返回第一个大于等于k值位置的迭代器 | |
返回第一个大于k值位置的迭代器 | |
返回<第一个大于等于val值位置的迭代器,第一个大于k值位置的迭代器> |
⑥map的元素访问
函数声明 | 功能介绍 |
返回map中key对应的value |
operator[]底层是用insert来实现的
insert返回的是一个pair,pair::frist是一个迭代器它指向新插入的元素(插入成功),要么指向和key相同的的元素(插入失败,key已经存在)。pair::second是true或者false
也就是说我们在使用operator[]时,即使没有找到,也会把这个key插入进去。它有插入、修改、查找的功能。
⑦补充
- inser插入的元素的类型是pair<const key_type,mapped_type>
虽然我们定义的value_type里的pair的第一个模板参数是const类型的,但是我们在传参不必传const类型,pair内部会转化为const类型。
map<string, string> m;
m.insert(pair<string, string>("sort", "排序"));
之所以可以在内部可以转化为const类型,是因为它的拷贝构造函数是函数模板。
拷贝构造函数里初始化列表为:frist(pr.frist),second(pr.second)
pr的frist即使是非const的,也可以用来拷贝构造给const的frist。
pair里有一个方法叫函数模板make_pair,函数模板可以自己推演出模板参数,所以,我们一般这样使用插入元素
map<string, string> m;
m.insert(make_pair("sort", "排序"));
⑧map的使用举例
void Test2()
{
map<string, string> m;
m.insert(make_pair("sort", "排序"));
m.insert(make_pair("insert", "插入"));
m.insert(make_pair("erase", "删除"));
m.insert(make_pair("swap", "交换"));
m.insert(make_pair("find", "查找"));
for (const auto& a : m)
{
cout << a.first << ":" << a.second << endl;
}
}
void Test3()
{
//统计水果出现的次数
vector<string> v = { "西瓜","西瓜","水蜜桃","火龙果","香蕉","水蜜桃","火龙果","西瓜","水蜜桃","香蕉","西瓜","火龙果", };
map<string, int> m;
for (const auto& a : v)//插入
{
m[a]++;
}
for (const auto& a : m)//查看出现次数
{
cout << a.first << ": " << a.second << endl;
}
}
4、multimap
(1)multimap的介绍
multiset与set主要的差别就在于它允许插入重复的元素,以及它没有实现operator[]。其他接口与map也没有什么差别。
完结。。。。。