关联容器

关联容器和顺序容器的本质区别:关联容器是通过键存取和读取元素、顺序容器通过元素在容器中的位置顺序存储和访问元素。因此,关联容器不提供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 = {,,,,};
set<int> st;//空集合
st.insert(arr.begin(),arr.end());//添加一个范围
st.insert();//添加一个元素
map<string,int>mp;
mp.insert({"hh",});
mp["tt"] = ;

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, });
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(, hasher, equalId);//参数是桶大小、哈希函数指针、相等性判断

C++基础之关联容器的更多相关文章

  1. C++学习基础四——顺序容器和关联容器

    —顺序容器:vector,list,queue1.顺序容器的常见用法: #include <vector> #include <list> #include <queue ...

  2. C/C++基础----关联容器

    基本属性 与顺序容器的差别,按照关键字来保存和访问,而顺序容器是按照容器中的位置来顺序保存和访问. map:每个元素是一对键值(key-valye)组合:set每个元素只包含关键字.. 每个根据关键字 ...

  3. c++基础(五)——关联容器

    1.关联容器 关联容器中的元素时按照关键字来保存和访问的,与之相对的,顺序容器中的元素时按它们在容器中的位置来顺序保存和访问的.两个主要关联容器是 map 和 set.标准库提供了8个关联容器,这8个 ...

  4. stl中顺序性容器,关联容器两者粗略解释

    什么是容器 首先,我们必须理解一下什么是容器,在C++ 中容器被定义为:在数据存储上,有一种对象类型,它可以持有其它对象或指向其它对像的指针,这种对象类型就叫做容器.很简单,容器就是保存其它对象的对象 ...

  5. c++的关联容器入门(map and set)

    目录 std::map std::set C++的关联容器主要是两大类map和set 我们知道谈到C++容器时,我们会说到 顺序容器(Sequence containers),关联容器(Associa ...

  6. ndk学习之c++语言基础复习----C++容器、类型转换、异常与文件流操作

    继续来复习C++,比较枯燥,但是这是扎实掌握NDK开发的必经之路,不容小觑. 容器: 容器,就是用来存放东西的盒子. 常用的数据结构包括:数组array, 链表list, 树tree, 栈stack, ...

  7. STL之关联容器

    关联容器包含map.set.multimap.multiset. 关联容器的特点是明显的,相对于顺序容器,有如下特点: 1.其内部是采用非线性的二叉树结构,具体的说是红黑树的结构原理实现的. 2.se ...

  8. STL_关联容器 VS C++ hashmap

    红黑树和哈希表区别: http://m.blog.csdn.net/article/details?id=52133283 关于STL中关联容器的几个问题: (1)为何map和set的插入删除效率比用 ...

  9. C++ 关联容器

    <C++ Primer 4th>读书笔记 关联容器和顺序容器的本质差别在于:关联容器通过键(key)存储和读取元素,而顺序容器则通过元素在容器中的位置顺序存储和访问元素. 关联容器(Ass ...

随机推荐

  1. Python --深入浅出Apriori关联分析算法(二) Apriori关联规则实战

    上一篇我们讲了关联分析的几个概念,支持度,置信度,提升度.以及如何利用Apriori算法高效地根据物品的支持度找出所有物品的频繁项集. Python --深入浅出Apriori关联分析算法(一) 这次 ...

  2. ASP.NET Core Basic 1-1 WebHost与项目配置

    .NET Core ASP.NET Core Basic 1-1 本节内容为WebHost与项目配置 项目配置文件 我们可以很清楚的发现在我们的文件中含有一个Json文件--appsettings.j ...

  3. C#中的变量祥解

    一.C#数据类型: A:值类型 值类型变量可以直接分配一个值,它是从System.ValueType派生而来,值类型直接包含数据,比如int,char,float,他们分别存储整型数据,字符,浮点数, ...

  4. Java集合框架之Set接口浅析

    Java集合框架之Set接口浅析 一.java.util.Set接口综述: 这里只对Set接口做一简单综述,其具体实现类的分析,朋友们可关注我后续的博文 1.1Set接口简介 java.util.se ...

  5. ESXi安装报错,No network adapters were detected...

    转载请在文章开头附上原文链接地址:https://www.cnblogs.com/Sunzz/p/11438066.html 报错内容 No network adapters No Network a ...

  6. log4j配置相对路径

    整理自网上: 一般在我们开发项目过程中,log4j日志输出路径固定到某个文件夹,这样如果我换一个环境,日志路径又需要重新修改,比较不方便, 1.log4j的FileAppender本身就有这样的机制, ...

  7. SPOJ - 3267. D-query 主席树求区间个数

    SPOJ - 3267 主席树的又一种写法. 从后端点开始添加主席树, 然后如果遇到出现过的元素先把那个点删除, 再更新树, 最后查询区间就好了. #include<bits/stdc++.h& ...

  8. [USACO07OCT]障碍路线 & yzoj P1130 拐弯 题解

    题意 给出n* n 的图,A为起点,B为终点,* 为障碍,.可以行走,问最少需要拐90度的弯多少次,无法到达输出-1. 解析 思路:构造N * M * 4个点,即将原图的每个点分裂成4个点.其中点(i ...

  9. 用C#实现的几种常用数据校验方法整理(CRC校验;LRC校验;BCC校验;累加和校验)

    CRC即循环冗余校验码(Cyclic Redundancy Check):是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定.循环冗余检查(CRC)是一种数据传输检错 ...

  10. Linux音频编程(二)声卡介绍

    一.声卡 1.声卡是audio interface,它含有hardware buffer,而这个hardware buffer是在声卡里面,不是内存.声卡的缓存是环状的,则ALSA中是将数据分成连续的 ...