关联容器
-
关联容器中的元素按关键字来保存和访问。
-
标准库提供8个关联容器,map和multimap定义在头文件map中,set和multiset定义在头文件set中,无序容器则定义在头文件unordered_map和unordered_set中。
按关键字有序保存元素 map 关联数组;保存关键字-值对,关键字不可重复出现。 set 关键字即值,即只保存关键字的容器,关键字不可重复出现。 multimap 关键字可重复出现的map multiset 关键字可重复出现的set 无序集合 unordered_map 用哈希函数组织的map unordered_set 用哈希函数组织的set unordered_multimap 哈希函数组织的map,关键字可重复出现 unordered_multiset 哈希函数组织的set,关键字可重复出现
使用关联容器
-
使用map
//可以直接列表初始化 map<string, size_t> word_count = {{"a", 1}, {"b", 2}...}; // 也可以先初始化空map,再添加键和值 map<string, size_t> word_count; string word; while (cin >> word) ++word_count[word];
-
使用set
// 单词计数程序,排除部分单词 map<string, size_t> word_count; set<string> exclude = {"The", "But", "And", "Or", "An", "A", "the", "but", "and", "or", "an", "a"}; string word; while (cin >> word) if (exclude.find(word) == exclude.end()) // 检查单词是否在忽略集中,find返回一个迭代器,在set中指向该关键字,否则返回尾后迭代器。这里,排除在exclude里面的单词。 ++word_count[word];
关联容器概述
-
定义关联容器:定义一个map时,必须既指明关键字类型又指明值类型;而定义一个set时,只需指明关键字类型,因为set中没有值。
map<string, size_t> word_count; // string到size_t的空map,关键字是string类型,值是size_t类型。 set<string> exclude = {"The", "But", "And", "Or", "An", "A", "the", "but", "and", "or", "an", "a"}; map<string, string> authors = { {"Joyce", "James"}, {"Austen", "Jane"}, {"Dickens", "Charles"} }; // 初始化muitimap或multiset // 定义一个有20个元素的vector,保存0到 vector<int> ivec; for (vector<int>::size_type i = 0; i != 10; ++i) { ivec.push_back(i); ivec.push_back(i); // 每个数重复保存一次 } // iset包含来自ivec的不重复的元素;miset包含所有20个元素 set<int> iset(ivec.cbegin(), ivec.cend()); // ivec初始化iset,iset也只有10个元素,即ivec中的不同元素 multiset<int> miset(ivec.cbegin(), ivec.cend()); // ivec初始化miset,miset有20个元素 cout << ivec.size() << endl; cout << iset.size() << endl; cout << miset.size() << endl;
-
关键字类型的要求:对于有序关联容器,关键字类型必须定义元素比较的方法,即传递给排序算法的可调用对象必须满足与关联容器中关键字一样的类型要求。
-
pair类型
-
pair标准库类型定义再头文件utility中;
-
一个pair保存两个数据成员;
-
pair类似容器,是一个用来生成特定类型的模板;
pair<string, string> anon; // 保存两个string pair<string, size_t> word_count; // 保存一个string和一个size_t pair<string, vector<int>> line; // 保存string和vector<int> pair<string, string> author{"James", "Joyce"}; // 保存两个string for (const auto &w : word_count) cout << w.first << "occurs" << w.second << ((w.second > 1) ? "times" : "time") << endl;
-
创建pair对象的函数
pair<string, int> process(vector<string> &v) { if (!v.empty()) return {v.back(), v.back().size()}; // 列表初始化 else return pair<string, int>(); // 隐式构造返回值 } // 用make_pair生成pair对象 if (!v.empty()) return make_pair(v.back(), v.back().size());
-
关联容器操作
-
关联容器额外的类型别名
key_type 此容器类型的关键字类型 mapped_type 每个关键字关联的类型,只适用于map value_type 对于set,与key_type相同,对于map,为pair<const key_type, mapped_type> - 由于不能改变一个元素的关键字,因此这些pair的关键字部分是const的。
-
关联容器迭代器
-
解引用一个关联容器迭代器时,得到一个类型为容器的value_type的值的引用;
// 获得指向word_count中一个元素的迭代器,*map_it是指向一个pair<const string, size_t>对象的引用 auto map_it = word_count.begin(); cout << map_it -> first; // 打印此元素的关键字 cout << " " << map_it -> second // 打印此元素的值 map_it -> first = "new key"; // 错误!关键字是const的 ++map_it -> second; // 正确:可以通过迭代器改变元素
-
set的迭代器是const的,即set的关键字是只读的。
-
遍历关联容器:
auto map_it = word_count.cbegin(); // 获得一个指向首元素的迭代器 while (map_it != word_count.cend()) { cout << map_it -> first << " occurs " << map_it -> second << " times " <<endl; // 解引用 ++map_it; // 递增迭代器,移动到下一个元素 }
-
**关联容器和算法:**通常不对关联容器使用泛型算法。在实际编程中,如果真的要对一个关联容器使用算法,要么将它当作一个源序列,要么当成一个目的位置。
-
-
添加元素:
操作名称 相关描述 c.insert(v) 如果c不是 multi
,返回的是一个pair
,first
是指向插入关键字的迭代器,指向的是一个map<t1,t2>::iterator
,second
是一个布尔值,为真则进行插入,反之,则没有进行插入。c.emplace(args)) 如果是 multi
,那么就只是返回一个指向新元素的迭代器。c.insert(b.e) b,e
表示的是一个c::value_type
的迭代器范围。返回void
c.insert({}) 返回的是 void
c.insert(p,v) 指定位置进行插入,返回同 insert(v)
c.emplace(p.args) 同上 -
关联容器的insert成员向容器添加一个元素或一个元素范围。由于map和set包含不重复的关键字,因此插入一个已经存在的元素对容器没有任何影响。
vector<int> ivec = {2, 4, 6, 8, 2, 4, 6, 8}; // ivec有8个元素 set<set> set2; // 空集合 set2.insert(ivec.cbegin(), ivec.cend()); // set2有4个元素 set2.insert({1, 3, 5, 7, 1, 3, 5, 7}); // set2有8个元素 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));
-
-
删除元素:
删除操作 作用 c.erase(k) 从c中删除每个关键字为k的元素,返回一个size_type值,指出删除元素的数量 c.erase§ 从c中删除迭代器p指向的元素,p须指向真实元素,返回指向p后元素的迭代器 c.erase(b,e) 删除迭代器对b和e所表示的范围中的元素,返回e -
map的下标操作
-
map的下标运算符接受一个索引,获取与此关键字相关联的值。与其他下标运算符不同的是,如果关键字不在map中,会为它创建一个元素并插入到map中,关联值将进行值初始化。
```c++
map<string, size_t> word_count;
word_count["Anna"] = 1; // 插入一个关键字为Anna的元素,关键值进行值初始化;然后将1赋予它
```
-
与vector与string不同,map的下标运算符返回的类型与解引用map迭代器得到的类型不同。
-
操作名称 相关描述 c[k] k是关键字,如存在返回 mapped_type
,若不存在,创建并进行值初始化。c.at(k) 返回 mapped_type
。会进行参数检查,如果k不存在,抛出out_of_range
-
访问元素
操作 描述 c.find(k) 有返回指向第一个关键字为k的迭代器,没有返回 end
c.count(k) 返回关键字k出现的次数。 c.lower_bound(k) 返回第一个不小于k的元素的迭代器 c.upper_bound(k) 返回第一个大于k的元素的迭代器 c.equal_range(k) 返回与匹配的迭代器范围,是一个 pair
类型的对象。注意,first
是指向第一个与k匹配的元素的迭代器,second
是指向与k匹配的最后一个元素的后一个元素。
无序容器
-
无序关联容器使用哈希函数和关键字类型的==运算符;
-
无序容器的底层实现:
- 通过哈希函数计算哈希值;
- 性能取决于哈希函数的选择,解决冲突的办法。
-
无序容器的操作:管理桶
-
无序容器在组织上为一组桶,每个桶保存零个或多个元素,无序容器使用一个哈希函数将元素映射到桶,容器将具有一个特定哈希值的所有元素都保存在相同的桶里;
-
为访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶,当一个桶保存多个元素时,需要顺序搜索这些元素来进行查找。
操作名称 相关描述 桶接口 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(),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_count
c.reverse(n) 重新存储,使得c可以保存n个元素而不需要 rehash
-
-
无序容器对关键字的要求
-
使用
hash<key_type>
类型对象来为每一个元素通过哈希函数生成哈希值; -
标准库为内置类型(指针,一些标准库类型,
string
,智能指针)提供了hash
模板。也就是说这些类型可以直接作为key_type
; -
对于自定义的类型我们需要自己进行构造
hash
模板,或者在构造容器时使用重载的版本:{ size_t hasher(const Sales_data &sd) { return hash<string>()(sd.isbn());//通过这个作为它的哈希值。注意哈希值的返回值是size_t } }
-
由以上的重载操作,我们可以对自定义类型构造无序容器:
{ using sd_multiset = unordered_multiset<Sales_data,decltype(hasher)*,decltype(eqOp)*>; sd_multiset bookStore(42,hasher,eqOp);//第一个参数表示的是桶的数量。第二个和第三个表示hash函数和比较操作。 //如果我们的类定义了== // 我们可以只对hasher进行重载。 unordered_multiset<f,decltype(fhasher)*>fset(10,fhasher); }
-