关联容器
关联容器和顺序容器的本质区别:关联容器是通过键存取和读取元素、顺序容器通过元素在容器中的位置顺序存储和访问元素。因此,关联容器不提供front、push_front、pop_front、back、push_back以及pop_back,此外对于关联容器不能通过容器大小来定义,因为这样的话将无法知道键所对应的值什么。关联容器支持高效的关键字查找和访问,主要是map和set两类。标准库提供了8个关联容器:
map | 关联数组:保存键值对,每个元素是pair类型,关键字起到索引的作用,值则表示与索引相关联的数据 |
set | 大小可变的集合,支持通过键实现的快速读取,只有关键字,没有值 |
multimap | 支持同一个键多次出现的 map 类型 |
multiset | 支持同一个键多次出现的 set 类型 |
unordered_map | 使用hash函数组织的map |
unordered_set | 使用hash函数组织的set |
unordered_multimap | 使用hash函数组织的map,关键字可重复出现 |
unordered_multiset | 使用hash函数组织的set,关键字可重复出现 |
一个map或set中关键字必须是唯一的,即一个关键字对应唯一一个值,但是multimap或multiset没有此限制,他们允许多个值对应一个关键字。
有序容器的关键字类型,默认情况下使用<运算符来比较关键字,也可以向算法提供自己定义的操作,但是操作必须在关键字类型上定义一个严格弱序,尖括号中每个类型仅仅表示一个类型,当创建对象时,才会以构造函数参数的形式提供真正的比较操作。
typedef struct Sales{ int id; }; //自定义的比较函数 bool compareId(const Sales &s1, const Sales &s2){ return s1.id < s2.id; } //将decltype作用于函数时,返回函数类型,而非函数指针,因此要显示加*来指示它是函数指针;使用compareId来初始化对象表示,使用bookstore添加对象是,使用该函数来排序 multiset<Sales, decltype(compareId)*>bookstore(compareId);
pair类型
pair类型定义在utility头文件中。
pair<T1, T2> p1; | 创建一个空的 pair 对象,它的两个元素分别是T1 和 T2 类型,采用值初始化 |
pair<T1, T2> p1(v1, v2); | 创建一个 pair 对象,它的两个元素分别是 T1 和 T2 ,其中 first 成员初始化为 v1,而 second 成员初始化为 v2 |
pair<T1, T2> p1={v1, v2}; | 等价于p1(v1, v2);新增加 |
make_pair(v1, v2) | 以 v1 和 v2 值创建一个新 pair 对象,其元素类型分别是 v1 和 v2 的类型 |
p1 < p2 | 两个 pair 对象之间的小于运算,其定义遵循字典次序:如果 p1.first < p2.first 或者 !(p2.first < p1.first) && p1.second < p2.second,则返回 true |
p1 == p2 | 如果两个 pair 对象的 first 和 second 成员依次相等,则这两个对象相等。该运算使用其元素的 == 操作符 |
p.first | 返回 p 中名为 first 的(公有)数据成员 |
p.second | 返回 p 的名为 second 的(公有)数据成员 |
关联容器还定义了类型:key_type:此容器类型的关键字类型;mapped_type:每个关键字关联的类型,只适用于map;value_type:对于set,与key_type相同,对于map,为pair<const key_type, mapped_type>。
注意:
- map的value_type是一个pair,我们可以改变pair的值,但不能改变关键字的值。
- 虽然set定义了iterator和const_iterator类型,但是这两种类型都只能读set中的元素,而不允许改变它的值。
我们通常不对关联容器使用泛型算法。因为,首先关键字是const表示不能将它传递给修改或重排容器元素的算法;虽然关联容器可用于只读算法,但是,这类算法很多要搜索序列,而关联容器中元素不能通过关键字进行快速查找(O(1)),因此,对其使用泛型搜索算法几乎总是坏主意。例如,泛型算法中的find,它会顺序搜索,但是关联容器定义了专用的find成员函数,它的效率会比泛型算法find快很多。
给容器添加元素
使用insert成员向容器添加元素或元素范围。
vector<int> arr = {0,1,2,3,4}; set<int> st;//空集合 st.insert(arr.begin(),arr.end());//添加一个范围 st.insert(5);//添加一个元素
map<string,int>mp; mp.insert({"hh",1}); mp["tt"] = 3;
insert返回的值依赖于容器类型和参数。对于不包含重复关键字的容器,添加单一元素的insert和emplace返回一个pair,pair的第一个元素是指向给定关键字的迭代器,另一个是bool类型的变量,表明插入成功与否,如果元素已存在,insert什么也不做,返回值bool部分为false。
m.insert(e) | e 是一个用在 m 上的 value_type 类型的值。如果键(e.first)不在 m 中,则插入一个值为 e.second 的新元素;如果该键在 m 中已存在,则保持 m 不变。该函数返回一个 pair 类型对象,包含指向键为 e.first 的元素的 map 迭代器,以及一个 bool 类型的对象,表示是否插入了该元素 |
m.insert(beg,end) | beg 和 end 是标记元素范围的迭代器,其中的元素必须为 m.value_type 类型的键-值对。对于该范围内的所有元素,如果它的键在 m 中不存在,则将该键及其关联的值插入到 m。返回void 类型 |
m.insert(iter, e) | e 是一个用在 m 上的 value_type 类型的值。如果键(e.first)不在 m 中,则创建新元素,并以迭代器 iter 为起点搜索新元素存储的位置。返回一个迭代器,指向 m 中具有给定键的元素 |
统计单词出现次数的程序:
map<string, int> word_count; string word; while (cin >> word) { auto ret = word_count.insert({ word, 1 }); if (!ret.second)//如果没插入成功,证明原来已经存在键值,将统计值+1 { ++ret.first->second;// first是一个迭代器,指向插入的键 } }
map的下标操作
map和unordered_map提供下标操作和对应的函数at。set不支持下标操作,因为没有与关键字关联的"值",multimap和unordered_multimap也没有下标操作,因为有多个值与一个关键字关联。
使用下标访问 map 与使用下标访问数组或 vector 的行为截然不同:用下标访问不存在的元素将导致在 map 容器中添加一个新元素,它的键即为该下标值。通常来说,下标操作符返回左值。它返回的左值是特定键所关联的值。
有别于 vector 或 string 类型,map 下标操作符返回的类型与对 map 迭代器进行解引用获得的类型不相同。显然,map 迭代器返回 value_type 类型的值——包含 const key_type 和 mapped_type 类型成员的 pair 对象;下标操作符则返回一个 mapped_type 类型的值。
删除元素
- c.erase(k) 删除m中键为k的元素。返回值为被删除元素的个数,对于map容器而言,其值必然是0或1。
- c.erase(p) 从m中删除迭代器p所指向的元素。返回值为void类型。
- c.erase(b,e) 从m中删除一段由一对迭代器范围的元素。返回值为void类型。
访问元素
关联容器提供多种查找指定元素的方法。如果只关心元素是否在容器中,使用find最好。对于不允许重复关键字的容器,find和count没有什么区别;但是对于允许重复关键字的容器,count可以同具有相同关键字的元素的个数。
下面是关联容器中查找元素的操作:(lower_bound和upper_bound不适用于无序容器)
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中下标操作会将不存在的关键字插入到map中,当我们不需要查找时去插入关键字时,就不能使用它,此时需要使用find函数。
如果一个multimap或multiset中有多个元素具有给定关键字,则这些元素在容器中会相邻存储。此时我们要找到某个关键字的所有元素时,有下面三种方法:
方法一:通过count和find来找到关键字对应的所有元素
//建立作者及其作品的容器 while(true){ string author, work; cout << "enter authors name or end the input" << endl; if(!getline(cin, author))break; cout << "enter authors works" << endl; while (cin >> work)authors.insert(make_pair(author, work)); cin.clear(); } //需要查找的关键字(作者) string search_item("Alain de Botton"); //统计该关键字中元素的个数,即作品数 auto entries = authors.count(search_item); //作者的第一部作品 auto iter = authors.find(search_item); //循环找到所有作品 while (entries){ cout << iter->second << endl;//输出每个作品 ++iter; --entries; }
方法二:通过lower_bound和upper_bound来找到关键字对应的所有元素
lower_bound返回第一个具有该关键字的元素位置,upper_bound返回最后一个与该关键字匹配的元素位置之后的一个位置。如果没有该关键字,则两个函数返回值相同;当关键字大于容器中的任何一个关键字,则会返回尾后迭代器。
//循环找到所有作品 for (auto be = authors.lower_bound(search_item), ed = authors.upper_bound(search_item); be != ed; ++be){ cout << be->second << endl;//输出每个作品 }
方法三:通过equal_range来找到关键字对应的所有元素
本质上和第二个方法相同。
//循环找到所有作品 for (auto range = authors.equal_range(search_item); range.first != range.second; ++range.first){ cout << range.first->second << endl;//输出每个作品 }
单词转换程序的实例:http://blog.csdn.net/jy_95/article/details/47807789
无序容器
新标准定义了四个无序容器:unordered开头的容器,使用hash函数来构造。
无序容器也支持与有序容器相同的操作,例如insert和find;因此通常可以使用无序容器来替换有序容器。
类 型 | 值 |
X::hasher | Hash,一个这样的二元函数对象,即如果hf的类型为Hash,k的类型为Key,则hf(k) 的类型为std::size_t |
X::local_iterator | 一个类型与X::iterator相同的迭代器,但只能用于一个桶 |
X::const_local_iterator | 一个类型与X::const_iterator相同的迭代器,但只能用于一个桶 |
管理桶
由于无序关联容器的实现是基于hash函数,那么就需要解决冲突问题,解决冲突的办法比较常用有开放地址法和拉链法。在C++的unordered_map当中,采用的是”桶”的方法,也就是出现冲突的元素都存放在一个桶里。
无序容器存储上组织为一个桶,可以存放零个或多个元素(因此,不同关键字的元素也可能在一个桶中,此时定位到那个桶后,在顺序搜索桶中的元素来找到目标元素),如果允许重复关键字,则关键字相同的元素也存放在一个桶中。因此,无序容器的性能取决于hash函数的质量和桶的数目和大小。
下面是管理桶的一些函数:
操 作 | 描 述 |
X(n, hf, eq) |
创建一个至少包含n个桶的空容器,并将hf用作哈希函数,将eq用作键值相等谓词。 如果省略了eq,则将key_equal( )用作键值相等谓词;如果也省略了hf,则将hasher( )用作哈希函数 |
X a(n, hf, eq) |
创建一个名为a的空容器,它至少包含n个桶,并将hf用作哈希函数,将eq用作键值相等谓词。 如果省略eq,则将key_equal( )用作键值相等谓词;如果也省略了hf,则将hasher( )用作哈希函数 |
X(i, j, n, hf, eq) |
创建一个至少包含n个桶的空容器,将hf用作哈希函数,将eq用作键值相等谓词,并插入区间[i, j]中的元素。 如果省略了eq,将key_equal( )用作键值相等谓词;如果省略了hf,将hasher( )用作哈希函数;如果省略了n,则包含桶数不确定 |
X a(i, j, n, hf, eq) |
创建一个名为a的的空容器,它至少包含n个桶,将hf用作哈希函数,将eq用作键值相等谓词,并插入区间[i, j]中的元素。 如果省略了eq,将key_equal( )用作键值相等谓词;如果省略了hf,将hasher( )用作哈希函数;如果省略了n,则包含桶数不确定 |
b.hash_function( ) | 返回b使用的哈希函数 |
b.key_eq( ) | 返回创建b时使用的键值相等谓词 |
桶接口 | |
b.bucket_count( ) | 返回b包含的桶数 |
b.max_bucket_count ( ) | 返回一个上限数,它指定了b最多可包含多少个桶 |
b.bucket(k) | 返回键值为k的元素所属桶的索引 |
b.bucket_size(n) | 返回索引为n的桶可包含的元素数 |
桶迭代 | |
b.begin(n) | 返回一个迭代器,它指向索引为n的桶中的第一个元素 |
b.end(n) | 返回一个迭代器,它指向索引为n的桶中的最后一个元素 |
b.cbegin(n) | 返回一个常量迭代器,它指向索引为n的桶中的第一个元素 |
b.cend(n) | 返回一个常量迭代器,它指向索引为n的桶中的最后一个元素 |
hash策略 | |
b.load_factor() | 返回每个桶包含的平均元素数 |
b.max_load_factor() | 返回负载系数的最大可能取值;超过这个值后,容器将增加桶 |
b.max_load_factor(z) | 可能修改最大负载系统,建议将它设置为z |
a.rehash(n) | 将桶数调整为不小于n,并确保a.bucket_count( )> a.size( ) / a.max_load_factor( ) |
a.reserve(n) | 等价于a.rehash(ceil(n/a.max_load_factor( ))),其中ceil(x)返回不小于x的最小整数 |
默认情况下,无序容器使用关键字类型的==运算符来比较元素,它们还使用一个hash<key_type>类型对象来生成每个元素的hash值。(标准库为每个内置对象,包括指针提供了hash模板;并且,为一些标准库类型定义了hash,例如string、智能指针)
那么如何使用自定义类型定义无序容器:
size_t hasher(const Sales &sl){ return hash<int>()(sl.id);//hash<int>是hash函数的函数对象size_t operator()(int _x) } bool equalId(const Sales &s1, const Sales &s2){ return s1.id == s2.id; } unordered_multiset<Sales, decltype(hasher)*, decltype(equalId)*>bookstore(10, hasher, equalId);//参数是桶大小、哈希函数指针、相等性判断