文章目录
第11章 关联容器
- 关联容器 元素按照关键字来保存和访问
- 顺序容器 容器中的位置来顺序保存和访问
- 关联容器
- map 关键字-值 (key-value)
- 关键字:索引的作用
- 值:与索引相关联的数据
- set 每个元素只包含一个关键字
- 高效关键字索引作用
- map 关键字-值 (key-value)
表11.1 关联容器类型 | |
---|---|
按关键字有序保存元素 | 头文件map和set中 |
map | 关键数组:保存关键字-值 对 |
set | 关键字即值,即只保存关键字的容器 |
multimap | 关键字可重复出现的map |
multiset | 关键字可重复出现的set |
无序集合 | 头文件unordered_map和unordered_set中 |
unordered_map | 用哈希函数组织的map |
unordered_set | 用哈希函数组织的set |
unordered_multimap | 哈希组织的map ,关键字可以重复出现 |
unordered_multiset | 哈希组织的set ,关键字可以重复出现 |
11.1 使用关联容器
- map 关联数组
- 下标 关键字
- set 关键字简单集合
- 用于判断某个值是否存在
- 用于判断某个值是否存在
- 使用map
- 单词计数程序
-
- 模板 指定关键字和值的类型
-
- 若关键字未在map中,下标运算符会创建一个新元素
-
- map使用pair的first成员保存关键字,用second成员保存对应的值
- 使用set
- 保存想忽略的单词
-
- 指定元素类型
-
- 列表初始化
-
- if检查单词是否在忽略集合中find(存在返回指向关键字的迭代器,不存在返回尾后迭代器)
11.2 关联容器概述
- 支持普通容器操作
- 不支持顺序容器位置相关操作(如push_front/push_back)
- 关联容器的迭代器都是双向的
11.2.1 定义关联容器
- 定义map时,指明关键字类型和值类型
- 定义set时,指明关键字类型
- 关联容器初始化
- 另一个同类型容器的拷贝
- 一个值范围(值可转换为容器所需类型)
- 值初始化 {花括号}
- set object={variable,variable,variable}
- map<keytype,valuetype> object={{key,value},{key,value},{key,value}}
- 初始化multimap或multiset
- map/set关键字须是唯一的
- multimap/multiset允许多个元素具有相同关键字
11.2.2 关键字类型的要求
-
有序容器 map/multimap/set/multiset
- 必须定义元素(关键字类型)比较的方法 <
-
有序容器的关键字类型
- 可提供自定义操作来代替关键字<运算符
- 严格弱序(小于等于)
-
- 两关键字不能同时小于等于对方
-
- 若k1<=k2&&k2<=k3,则k1<=k3
-
- 两关键字任一个都不 小于等于 另一个,则两关键字等价(相等)
-
-
使用关键字类型的比较函数
- 自定义操作 定义关联容器类型时提供此操作类型<尖括号中紧跟元素类型给出>
- 须提供两个类型 <关键字类型,比较操作类型>
- 关键字类型
- 比较操作类型 (函数指针类型)
- 构造函数参数传入排序函数指针
11.2.3 pair类型
- 创建pair对象时须提供两个类型名
- 默认构造函数对数据成员进行值初始化;也可以为每个成员提供初始化器
- pair有两个public的数据成员,分别为first和second
表11.2 pair上的操作 | |
---|---|
pair<T1, T2> p; | p 是一个pair ,两个类型分别为T1 和T2 的成员都进行了值初始化 |
pair<T1, T2> p(v1, v2); | p 是一个成员类型分别为T1 和T2 的pair;first 和second 分别用v1 和v2 进行初始化。 |
pair<T1, T2>p = {v1, v2}; | 等价于`p(v1, v2) |
make_pair(v1, v2); | 返回一个用v1和二元初始化的pair,pair 的类型从v1 和v2 的类型推断出来 |
p.first | 返回p 的名为first 的(公有)数据成员 |
p.second | 返回p 的名为second 的(公有)数据成员 |
p1 relop p2 | 运算关系符(<、>、<=、>=)按字典序定义:p1的first小于p2的(或p1的first不大于p2的情况下,p1的second小于p2的),p1<p2为true,关系运算符利用元素的<运算符的==运算符实现 |
p1 == p2 | 当first和second成员分别相等时,两个pair相等。相等性判断利用元素的==运算符实现 |
p1 != p2 | 不相等 |
- 创建pair对象的函数
- 函数返回pair
- 可对返回值进行列表初始阿化{花括号包围的初始化器}
- 或者进行隐式构造返回值
- 显示构造返回值
- 用make_pair来生成pair对象
- 函数返回pair
11.3 关联容器操作
表11.3 关联容器额外的类型别名 | |
---|---|
key_type | 此容器类型的关键字类型 |
mapped_type | 每个关键字关联的类型:只适用于map |
value_type | 对于set ,与key_type 相同;对于map ,为pair<const key_type, mapped_type> ; |
- 由于不能修改一个元素的关键字,pair的关键字部分是const的
- 使用作用域运算符来提取一个类型的成员
map<string,int>::key_type
- 只有map类型(unordered_map/unodered_multimap/multimap/map)定义了mapped_type
11.3.1 关联容器
- 解引用关联容器迭代器时,会得到一个类型为容器类型的value_type的值的引用
- 对于
map
,为pair<const key_type, mapped_type>
- first保存const关键字,second成员保存值
- 可以改变pair的值,但是不能改变关键字成员的值
- 对于
- set的迭代器是const的 是只读元素的值,不能修改
- 遍历关联容器
- map和set都支持begin和end操作 可用来遍历容器
- 迭代器按关键字升序遍历元素
- 关联容器和算法
- 关键字为const意味着不能将关联容器传递给修改或重排容器元素的算法
- 关联容器可用于只读取元素的算法
- 将关联容器当作源序列或一个目的位置
11.3.2 添加元素
-
插入一个已存在的元素对容器没有任何影响
-
对于给定关键字只有第一个带此关键字的元素才被插入到容器中
- insert(v.cbegin(),v.cend());//接受一对迭代器
- insert({1,2,3});//接受一个初始器列表
-
向map添加元素
- insert操作的元素类型是pair
word_count.insert({word, 1});
word_count.insert(make_pair(word, 1));
word_count.insert(pair<string, size_t>(word, 1));
word_count.insert(map<string, size_t>::value_type (word, 1));
表11.4 关联容器insert 操作 | |
---|---|
c.insert(v) c.emplace(args) | v 是value_type 类型的对象;args 用来构造一个元素。 对于map 和set ,只有元素的关键字不在c 中才插入(或构造)元素。函数返回一个pair ,包含一个迭代器,指向具有指定关键字的元素,以及一个指示插入是否成功的bool 值。对于multimap 和multiset 则会插入(或构造)给定元素,并返回一个指向新元素的迭代器 |
c.insert(b, e) c.insert(il) | b 和e 是迭代器,表示一个c::value_type 类型值的范围;il 是这种值的花括号列表。函数返回void 。对于 map 和set ,只插入关键字不在c 中的元素。对于multimap和multiset,则会插入范围中的每个元素 |
c.insert(p, v) c.emplace(p, args) | 类似insert(v) (或emplace(args)),但将迭代器p 作为一个提示,指出从哪里开始搜索新元素应该存储的位置。返回一个迭代器,指向具有给定关键字的元素。 |
- 检测insert的返回值
- 返回一个pair,first为一个指向具有给定关键字的迭代器,second为判断bool(当元素成功插入返回true,若关键字已存在则insert什么也不做bool返回false)
- 展开递增语句
++ret.first->second;//等价于
++((rect.first)->second);
- 向multiset或multimap添加元素
- multi容器中的关键字不必唯一,调用insert总会插入一个元素
11.3.3 删除元素
- 关联容器定义了三个版本的erase
- 接受一个迭代器 删除一个元素
- 接受一对迭代器 删除一个元素范围
- 接受一个key_type参数 删除所有匹配关键字的元素
- 返回实际删除元素的数量
- 对于multi容器返回值可能大于1,其他容器返回值总为0或1
表11.5 从关联容器删除元素 | |
---|---|
c.erase(k) | 从c 中删除每个关键字为k 的元素。返回一个size_type 值,指出删除的元素的数量 |
c.erase(p) | 从c 中删除迭代器p 指定的元素。p 必须指向c 中一个真实元素,不能等于c.end() 。返回一个指向p 之后元素的迭代器,若p 指向c 中的尾元素,则返回c.end() |
c.erase(b, e) | 删除迭代器对b 和e 所表示范围中的元素。返回e 。 |
11.3.4 map的下标操作
- map和unordered_map容器提供了下标运算符和对应的at函数
- set不支持下标,因为没有与关键字对应的值
- multimap和unordered_multimap也不能进行下标操作。因为一个关键字可能对应多个值
map和unordered_map的下标操作 | |
---|---|
c[k] | 返回关键字为k 的元素;如果k 不在c 中,添加一个关键字为k 的元素,对其进行值初始化 |
c.at(k) | 访问关键字为k 的元素,带参数检查;若k 不存在在c 中,抛出一个out_of_range 异常 |
- 使用下标操作的返回值
- 对map进行下标操作返回一个mapped_type对象(左值)
- 解引用一个map迭代器时,会得到一个value_type对象
- 若关键字未在map中,下标运算符会添加一个新元素
11.3.5 访问元素
- 关联容器 查找指定元素的方法
- 一个特定元素是否存在于容器中 find
- 统计有多少个元素有相同关键字 count
表11.7 在一个关联容器中查找元素的操作 | |
---|---|
lower_bound和upper_bound不适用于无序容器 | |
下标和at操作只适用于非const的map和unordered_map | |
c.find(k) | 返回一个迭代器,指向第一个关键字为k 的元素,若k 不在容器中,则返回尾后迭代器 |
c.count(k) | 返回关键字等于k 的元素的数量。对于不允许重复关键字的容器,返回值永远是0或1 |
c.lower_bound(k) | 返回一个迭代器,指向第一个关键字不小于k 的元素 |
c.upper_bound(k) | 返回一个迭代器,指向第一个关键字大于k 的元素 |
c.equal_range(k) | 返回一个迭代器pair ,表示关键字等于k 的元素的范围。若k 不存在,pair 的两个成员均等于c.end() 。 |
-
对map使用find代替下标操作
- 如果关键字还未在
map
中,下标操作会插入一个具有给定关键字的元素
- 如果关键字还未在
-
在
multimap或multiset
中查找元素multimap/multiset
中多个元素具有给定关键字,则元素在容器中相邻存储- 可利用
find
找到第一个关键字的迭代器位置,使用count
获取容器中该关键字的个数,设为循环次数对迭代器进行递增,即可获取该关键字所对应的所有值
-
一种不同的,面向迭代器的解决方法
- 关键字在容器中
lower_bound
返回的迭代器将指向第一个具有给定关键字的元素,upper_bound
返回的迭代器将指向最后一个匹配给定关键字的元素之后的位置; - 关键字的元素不在multimap中,
lower_bound
和upper_bound
返回相等的迭代器,指向一个不影响排序的关键字插入位置 lower_bound
和upper_bound
之间的迭代器范围,表示具有该关键字的元素的范围(可用for遍历);如果lower_bound
和upper_bound
返回相同的迭代器则给定关键字不在容器中- 若查找的元素具有容器中最大的关键字,则关键字的
upper_bound
返回尾后迭代器;若关键字不存在且大于容器中任何关键字,则lower_bound
和upper_bound
都返回尾后迭代器
- 关键字在容器中
-
equal_range函数
- 函数接受一个关键字,返回一个迭代器pair
- 若关键字存在,第一个迭代器指向第一个与关键字匹配的元素,即
pos.first
等价于beg;第二个关键字指向最后一个匹配元素之后的位置,即pos.second
等价于end - 若未找到匹配元素,则两个迭代器都指向关键字可以插入的位置
11.3.6 一个单侧转换的map
-
map的创建、搜索以及遍历
-
- 程序输入两个文件
-
- 第一个文件保存 string单词转换规则
-
- 第二个文件保存需被替换部分的文本
-
-
单词转换程序
word_transform
管理整个过程 接受两个ifstream参数(两个文件)buildMap
读取转换规则,创建一个map,保存每个单词到其转换内容的映射transform
接受一个string,若存在转换规则,返回转换后的内容
-
建立转换映射
>>
读取要转换的单词存入key中- getline读取该行剩余内容为要被转换成的文本
- 隐含忽略了一个单词在转换文件中出现多次的情况(若存在单词出现多次,则将最后一个对应短语存入trans_map)
-
生成转换文本
- find来确定给定string是否在map中
- 若存在,返回一个指向对应元素的迭代器,解引用迭代器,获得一个保存关键字和值的pair
- 若不存在,返回尾后迭代器
- find来确定给定string是否在map中
11.4 无序容器
- 4个无序关联容器,使用哈希函数和关键字类型的==运算符(而不是比较运算符)来组织元素
- 关键字类型没有明显序关系
- 无序容器通常更为简单也会有更好的性能
- 使用无序容器
- 哈希管理操作
- 无序容器也有与有序容器相同的操作
- 管理桶
- 无序容器使用一个哈希函数将元素映射到桶
- 访问一个元素,需先计算元素的哈希值
- 容器将具有一个特定哈希值的所有元素都保存在相同的桶中
- 若容器允许重复关键字,则具有相同关键字的元素也都会在同一个桶中
- 无需容器性能
- 哈希函数的质量
- 桶的数量和大小
- 对相同参数,哈希函数总是产生相同结果;理想情况下,每个特定值映射到唯一的桶;将不同的关键字的元素映射到相同的桶也是允许的
- 当一个桶保存多个元素时,需要顺序搜索这些元素来查找想要的那个
- 下表管理桶的成员函数允许我们查询容器的状态以及在必要时强制容器进行重组
表11.8 无序容器管理操作 | |
---|---|
桶接口 | |
c.bucket_count() | 正在使用的桶的数目 |
c.max_bucket_count() | 容器能容纳的最多的桶的数目 |
c.bucket_size(n) | 第n 个桶中有多少个元素 |
c.bucket(k) | 关键字为k 的元素在哪个桶中 |
桶迭代 | |
local_iterator | 可以用来访问桶中元素的迭代器类型 |
const_local_iterator | 桶迭代器的const 版本 |
c.begin(n) ,c.end(n) | 桶n 的首元素迭代器和尾后迭代器 |
c.cbegin(n) ,c.cend(n) | 与前两个函数类似,但返回const_local_iterator 。 |
哈希策略 | |
c.load_factor() | 每个桶的平均元素数量,返回float 值。 |
c.max_load_factor() | c 试图维护的平均桶大小,返回float 值。c 会在需要时添加新的桶,使得load_factor<=max_load_factor |
c.rehash(n) | 重组存储,使得bucket_count>=n ,且bucket_count>size/max_load_factor |
c.reverse(n) | 重组存储,使得c 可以保存n 个元素且不必rehash |
- 无序容器对关键字类型的要求
- 无序容器使用关键字类型的==运算符来比较元素
- 使用一个hash<key_type>类型对象来生成每个元素的哈希值
- 标准库为内置类型(包括指针)提供了hash模板,为一些标准库类型(如string/智能指针)定义了hash。故可直接定义关键字为这些类型的无序容器
- 不能直接定义关键字类型为自定义类类型的无序容器,不能直接使用哈希模板,必须提供自己的hash模板版本
- 不使用默认的hash,使用类似于为有序容器重载关键字类型的默认比较操作(需提供函数来替代==运算符和哈希计算函数)
//使用标准库hash类型(建立在string类型之上)对象来计算isbn成员哈希值
size_t hasher(const sales_datas &sd)
{
return hash<string>()(sd.isbn());
}
//通过比较isbn号来比较两个sales_data
bool eqOp(const sales_data &lhs,const sales_data &rhs)
{
return lhs.isbn()==rhs.isbn();
}
//定义类型别名 哈希和相等性判断操作
using sd_multiset=unordered_multiset<sales_data,decltype(hasher)*,decltype(eqop)*>;
//使用上述函数定义一个无序容器 参数为 桶大小,哈希函数指针和相等性判断运算符指针
sd_multiset bookstore(42,hasher,eqOp);
小结
- 顺序容器
- 通过位置访问元素
- 关联容器
- 关键字高效查找 提取元素
- 允许重复关键字的容器都包含multi
- 无论有序/无序容器中,具有相同关键字的元素都是相邻存储的
- 有序关联容器
- 使用比较函数(默认为关键字类型的<运算符)来比较关键字,将元素顺序存储
- 无序关联容器(unordered)
- 使用关键字类型==运算符和一个hash<key_type>类型的对象来组织元素
术语表
- 哈希函数 将给定类型的值映射到整型(size_t)值的函数。相等的值必须映射到相同的整数;不相等的值尽可能映射到不同的整数
- key_type 保存和提取关键字的类型
- mapped_type 映射类型定义的类型,就是映射中关键字关联的值的类型
- value_type 容器中元素的类型
- 严格弱序 可以比较任意两个值并确定哪个更小,若任何一个都不小于另一个,则认为两个值相等