C++_Primer11.associative_continer
关联容器
associative-container
关联容器和顺序容器有着根本的不同:关联容器中的元素是按关键字来保存和访问的。而顺序容器是按他们在容器中的位置来顺序保存和访问的。
因此关联容器不支持顺序容器的位置相关操作,比如 push_front, push_back.
两个主要的关联容器:map 和 set。map 是键值对形式;set 每个元素只有一个关键字,支持高效的关键字查询操作(检查一个给定的关键字是否在 set 中)。
标准库提供8个关联容器:
| 容器 | 说明 |
|---|---|
| 按关键字有序保存元素 | |
| map | 关联数组,保存键值对 |
| set | 关键字即值 |
| multimap | 关键字可重复出现的 map |
| multiset | 关键字可重复出现的 set |
| 无序集合 | |
| unordered_map | 用哈希函数组织的map |
| unordered_set | 用哈希函数组织的set |
| unordered_multimap | 哈希组织的map,关键字可重复出现 |
| unordered_multiset | 哈希组织的set,关键字可重复出现 |
使用关联容器
map 类型通常被称为关联数组(associative array).与正常数组类似,但其下标不必是整数。可以通过一个关键字而不是位置来查找值
根据关键字计数:
map<string, size_t> word_count;
set<string> exclude = {"the", "but", "and", "or", "an", "a"};
string word;
while (cin >> word){
if (exclude.find(word) == exclude.end()){
++word_count[word];
}
}
for (const auto& w: word_count){
cout << w.first << "\t:\t" << w.second << endl;
}
概述
关键字类型必须定义元素比较的方法,默认情况下标准库使用关键字类型的 < 运算符比较两个关键字。
(所以,有序关联容器默认按照字典序排列)
对于类类型,比如 Sales_data,没有定义 < 运算符,可以使用自定义比较函数:
bool compareIsbn(const Sales_data& lhs, const Sales_data& rhs){
return lhs.isbn() < rhs.isbn();
}
// 声明可重复集合,多条记录可以有相同的 isbn
multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);
pair 类型
在头文件 utility 中
保存两个数据成员:
pair<string, string> anon;
pair<string, size_t> word_count;
pair<string, vector<int>> line;
pair<string, string> p1("Tom", "John");
pair<string, string> p2 = {"Tom", "John"};
auto p3 = make_pair("Tom", "John");
cout << "p3: " << p3.first << ", " << p3.second << endl;
auto b = p1 > p2; // 按字典序比较,依次比较 first 和 second
函数返回 pair 类型:
pare<string, int>
process(vector<string>& v){
if (!v.empty()){
return {v.back(), v.back().size()};
// 或者:
return make_pair(v.back(), v.back().size());
} else {
return pair<string, int>();
}
}
操作
关联容器定义了以下下类型:
| 类型 | 说明 |
|---|---|
| key_type | 此容器类型的关键字类型 |
| mapped_type | 每个关键字关联的类型,只适用 map |
| value_type | 对于 set,与 key_type 相同 对于 map,为 pair<const key_type, mapped_type> |
set<string>::value_type v1; // string
set<string>::key_type v2; // string
map<string, int>::value_type v3; // pair<const string, int>
map<string, int>::key_type v4; // string
map<string, int>::mapped_type v5; // int
遍历关联容器
auto map_it = word_count.begin();
while (map_it != word_count.end()){
cout << map_it->first << ": " << map_it->second << endl;
// map_it->first 是 const 的,而 second 是可以改变的
++map_it->second;
++map_it;
}
// set
set<int> iset = {0,1,2,3,4,5};
set<int>::iterator set_it = iset.begin();
if (set_it != iset.end()){
*set_it = 42; // 错误,set 中的关键字是只读的
cout << *set_it << endl;
}
由于关联容器的关键字是只读的,所以对关联容器只能使用只读的泛型算法。
向关联容器插入元素
// set
vector<int> ivec = {0,1,2,3,4};
set<int> set2;
set2.insert(ivec.cbegin(), ivec.cend());
set2.insert({5,6,7});
// map
string word = "example";
word_count.insert({word, 1});
word_count.insert(make_pair(word, 1));
word_count.insert(pair<string, int>(word, 1));
word_count.insert(map<string, int>::value_type(word, 1));
| 插入操作 | 说明 |
|---|---|
| c.insert(v) c.emplace(args) |
v 是 value_type 类型;args 用来构造一个元素。 对于 map 和 set,只有元素关键字不在 c 中是才插入。 函数返回一个 pair,包含一个迭代器和一个 bool 值。 迭代器指向指定关键字的元素 |
| c.insert(b, e) c.insert(il) |
b 和 e 是迭代器,表示一个 c::value_type 类型的范围。 il 表示花括号列表。函数返回 void |
| c.insert(p, v) c.emplace(p, args) |
类似 c.insert(v),但插入的位置由迭代器 p 指定 |
第一种插入方法返回值类型是 pair<map<string, size_t>::iterator, bool> (假设 v 是 map<string, size_t>)
其中的 bool 值代表是否插入新元素,如果已经存在关键字,则不会插入,返回 false,否则为 true.
删除元素
| 操作 | 说明 |
|---|---|
| c.erase(k) | 删除所有关键字 k 的元素,返回 size_type 类型,表示删除元素的数量 |
| c.erase(p) | 删除迭代器 p 指定的元素,p 不能指向 c.end(),返回一个指向 p 之后的元素的迭代器 若 p 指向尾元素,则返回 c.end() |
| c.erase(b, e) | 删除迭代器 b 和 e 表示的范围中的元素,返回 e |
map的下标操作
访问 map 的操作:
- 下标运算符
- at 函数
当关键词不存在时,下标操作会插入一个新元素。
由于下标运算符可能插入一个新元素,我们只能对非 const 的 map 使用下标操作。
通常情况下,解引用一个迭代器返回的类型和下标运算符返回的类型是一样的,但对 map 则不一样:下标操作返回一个 mapped_type 对象,解引用 map 迭代器返回一个 value_type 对象。
与其他下标运算符相同,map 的下标运算符返回一个左值,可以对其操作:
++word_count["Anna"];
访问元素
set<int> iset = {0,1,2,3,4,5,6};
iset.find(2); // 返回一个迭代器,指向 key 为2的元素
iset.find(10); // 返回一个迭代器,等于 iset.end()
iset.count(2); // 1
iset.count(10); // 0
| 查找操作 | 说明 |
|---|---|
| c.find(k) | 查找 key 等于 k 的元素。若 k 不存在,返回尾后迭代器 |
| c.count(k) | 计数 |
| c.lower_bound(k) | 返回第一个关键字不小于 k 的元素 |
| c.upper_bound(k) | 返回第一个大于 k 的元素 |
| c.equal_range(k) | 返回一个迭代器 pair,表示关键字等于 k 的元素的范围。若 k 不存在,pair 的两个成员均等于 c.end() |
无需容器不支持 c.lower_bound, c.upper_bound。 |
multimap 和 multiset
对于允许重复关键字的容器,相同关键字的元素在容器中会相邻存储。
// 打印同一个作者的所有书
string search_item("侯捷");
auto entries = authors.count(search_item);
auto iter = authors.find(search_item);
while(entries){
cout << iter->second << endl;
++iter;
--entries;
}
// 使用 lower_bound 和 upper_bound
for (auto beg = authors.lower_bound(search_item),
end = authors.upper_bound(search_item);
beg != end; ++beg){
cout << beg->second << endl;
}
// 使用 equal_range
for (auto range = authors.equal_range(search_item);
range.first != range.second; ++range.first){
cout << range.first->second << endl;
}
无序容器
无序容器不是使用比较运算符来组织元素,而是用一个哈希函数和关键字类型的==运算符。
无序容器在存储上组织为一个桶,每个桶保存0个或多个元素,使用一个哈希函数将元素映射到桶。一个桶中的元素都属于同一个关键字。计算一个元素的哈希值和在桶中搜索通常是比较快的操作。但如果一个桶中保存了很多元素,那么查找一个特定元素就需要大量比较操作。
| 无序容器操作 | 说明 |
|---|---|
| 桶接口 | |
| 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.reserve(n) | 重组存储,使得 c 可以保存 n 个元素且不必 rehash |
关键字类型
默认情况下,无序容器使用关键字类型的=运算符比较元素。使用一个 hash<key_type> 类型的对象来生成每个元素的哈希值。key_type 可以是内置类型,string 和智能指针类型。
单词转换程序
将一段文字中的缩写替换为全写,缩写和全拼对应关系保存在一个文件中。
TODO:
p396
16.5节(p626)
#include <iostream>
#include <string>
#include <unordered_map>
#include <fstream>
#include <sstream>
using namespace std;
unordered_map<string, string> buildMap(string);
const string& transform(const unordered_map<string, string>&, const string&);
int main(int argc, char** argv){
string map_file("map.ini");
unordered_map<string, string> umap = buildMap(map_file);
string line;
string key;
while (getline(cin, line)){
istringstream linestrm(line);
while (linestrm >> key){
cout << transform(umap, key) << " ";
}
cout << endl;
}
return 0;
}
unordered_map<string, string> buildMap(string file_name){
unordered_map<string, string> umap;
ifstream strm(file_name);
string key, value;
while (strm >> key && getline(strm, value)){
if (value.size() > 1){
umap[key] = value.substr(1);
} else {
throw runtime_error("no rule for " + key);
}
}
return umap;
}
const string& transform(const unordered_map<string, string>& umap,
const string& key){
auto ele = umap.find(key);
if (ele != umap.cend()){
return ele->second;
}
return key;
}
小结
关联容器通过关键字高效查找和提取元素,共有8个关联容器,每个容器:
- 是 map 或 set
- map 使用键值对,set 只有关键字
- 关键字唯一或不要求
- 关键字有序或无序
有序容器默认使用<运算符顺序存储。无序容器使用==和一个 hash<key_type> 类型的对象来组织元素。
允许重复关键字的容器名字都包含 multi;无序容器都以 unordered 开头。
无论有序还是无序容器,具有相同关键字的元素都是相邻存储的。

浙公网安备 33010602011771号