在初阶阶段,我们已经接触过 STL 中的部分容器,比如: vector 、 list 、 deque 、 forward_list(C++11) 等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?关联式容器 也是用来存储数据的,与序列式容器不同的是,其 里面存储的是结构的键值对,在 数据检索时比序列式容器效率更高 。
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量 key 和 value , key 代表键值, value 表示与 key 对应的信息 。比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。
template
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(): first(T1()), second(T2())
{}
pair(const T1& a, const T2& b): first(a), second(b)
{}
};
键值对实质上就是一对放在结构体中的数据,这两个数据一一对应,找到了其中一个也就找到了另一个
根据应用场景的不桶, STL 总共实现了两种不同结构的管理式容器:树型结构与哈希结构。 树型结构的关联式容器主要有四种: map 、 set 、 multimap 、 multiset 。这四种容器的共同点是:使用平衡搜索树 ( 即红黑树 )作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。
1. set 是按照一定次序存储元素的容器2. 在 set 中,元素的 value 也标识它 (value 就是 key ,类型为 T) ,并且每个 value 必须是唯一的。 set 中的元素不能在容器中修改( 元素总是 const) ,但是可以从容器中插入或删除它们。3. 在内部, set 中的元素总是按照其内部比较对象 ( 类型比较 ) 所指示的特定严格弱排序准则进行排序。4. set 容器通过 key 访问单个元素的速度通常比 unordered_set 容器慢,但它们允许根据顺序对子集进行直接迭代。5. set 在底层是用二叉搜索树 ( 红黑树 ) 实现的。注意:1. 与 map/multimap 不同, map/multimap 中存储的是真正的键值对, set 中只放 value ,但 在底层实际存放的是由构成的键值对。 2. set 中插入元素时,只需要插入 value 即可,不需要构造键值对。3. set 中的元素不可以重复 ( 因此可以使用 set 进行去重 ) 。4. 使用 set 的迭代器遍历 set 中的元素,可以得到有序序列5. set 中的元素默认按照小于来比较6. set 中查找某个元素,时间复杂度为:logN7. set 中的元素不允许修改 ( 为什么 ?)8. set 中的底层使用二叉搜索树 ( 红黑树 ) 来实现。
我们先来看一下set的遍历
这时候我们就只能用迭代器去进行遍历了,无法使用[ ]方式,注意:1.范围for也本质是迭代器
2.set这个容器中的数据是不允许被修改的,因为其为一个搜索二叉树,不能修改
set的删除
这里我们补充一下:我们之前学习别的容器时是没有find的,我们当时使用的是算法中的find,而在这里我们采用的是容器中的find,容器中的find与算法之中的find最大的区别就是时间复杂度不同,算法的为N,这里的为logN,因为本质为树,所以我们尽量使用这里的find,更加高效
1. map 是关联容器,它按照特定的次序 ( 按照 key 来比较 ) 存储由键值 key 和值 value 组合而成的元素。2. 在 map 中,键值 key 通常用于排序和惟一地标识元素,而值 value 中存储与此键值 key 关联的内容。键值key和值 value 的类型可能不同,并且在 map 的内部, key 与 value 通过成员类型 value_type 绑定在一起,为其取别名称为pair:typedef pair value_type;3. 在内部, map 中的元素总是按照键值 key 进行比较排序的。4. map 中通过键值访问单个元素的速度通常比 unordered_map 容器慢,但 map 允许根据顺序对元素进行直接迭代( 即对 map 中的元素进行迭代时,可以得到一个有序的序列 ) 。5. map 支持下标访问符,即在 [] 中放入 key ,就可以找到与 key 对应的 value 。6. map 通常被实现为二叉搜索树 ( 更准确的说:平衡二叉搜索树 ( 红黑树 )) 。
map的遍历
map同样也支持遍历,我们这里通过匿名对象创建结点,不过这里因为遍历的值为两个,所以我们将其整合为结构体放入pair键值对中,从pair再去访问数据,而且map也是根据中序遍历默认顺序排序的,其实map与set是很相像的,区别是map中是两个值
事实上在工程中我们更多的是使用make_pair的方式去构造pair对象的,更加的简洁
我们通过阅读上面的说明可以得知,这个[ ]其实是一个迭代器,而且与插入函数insert密切相关,如果先插入一个元素,那么这个迭代器就指向新插入的元素,第二个参数置为true,代表插入成功,而若插入的元素本来就存在了,则这个迭代器就指向存在了的那个元素的迭代器,第二个参数值为false,代表插入失败
我们通过一个计数函数来了解
我们可以发现,这个计数函数完成了对于字符串的个数统计,采用的是遍历的方式
我们再来看这段代码,也是计数功能,可以发现相对上面的代码,这段代码我们利用的是插入函数insert,将数组放到auto中不断进行插入,而后将插入函数的返回值放入到一个pair当中,通过判断插入函数返回的第二个值是否为true来判断是否插入成功,若成功则说明是第一次接触到这个值,进行了插入,若失败则是因为map中已经存在了这个值,而后对其second进行++,当auto遍历结束则完成计数
下面正式引入我们的operator[]函数
mapped_type& operator[](count key_type& k)
{
return(*((this->insert(make_pair(k, mapped_type()))).first)).second;
//最内层(this->insert(make_pair(k, mapped_type()))),这其实就是我们的insert函数,其返回值为pair,也就是一个iterator
//下一层解引用pair,对这个iterator进行解引用,解引用又是一个pair
//最后一层整个对象再取一次second,也就是上层的mapped_type
//这里不用find而用insert的原因是用find假设map中没有k,无法返回,用insert
//1.如果k不在map中,则插入有pair,在返回映射对象的引用
//2.如果k不在map中,则插入失败,返回k所在节点的中的映射对象的引用
}
我们可以看到它的返回值很复杂,拆接下来可以分为3层,内层的insert函数返回的pair的first解引用成外部iterator,而后去调用second返回
我们再来看看用operator[]来进行计数的方式
string strs[] = { "西瓜", "黄瓜", "葡萄", "草莓", "黄瓜", "西瓜", "草莓", "西瓜", "西瓜", "苦瓜", "西瓜", "东瓜", "西瓜" };
mapcountMap;
for (auto& str : strs)
{
//1.如果水果不在map中,则operator[]会插入pair,返回映射对象(次数)的引用进行了++
//2.如果水果在map中,则operator[]返回水果对应的映射(次数)的引用,对它++
countMap[str]++;
}
直接对其++即可完成统计,原因是底层的insert若不存在则进行插入,若不存在则因为其返回的引用,也就是对应的次数,直接++即可
下面我们对operator[]操作进行展示
//map中的operator[]三层应用
//1.插入,2.查找k对应的映射对象,3.修改k对应的映射对象
//一般使用operator[]去 1.插入+修改。2.修改。一般不会进行查找,yinweikey如果不在就会进行插入
countMap["香蕉"];//插入
countMap["香蕉"] = 1;//修改
cout << countMap["香蕉"] << endl;//查找
countMap["哈密瓜"] = 5;//插入+修改
for (auto& e : countMap)
{
cout << e.first << "_" << e.second << endl;
}
std::mapdict;
dict.insert(make_pair("sort", "排序"));
dict["string"];//插入,但是一般不会这样用
dict["string"] = "字符串";//修改
dict["left"] = "左边"//插入+修改
事实上map中的operatop[]是一个极其特殊的操作符,可以完成许多操作
总结:1. map 中的的元素是键值对2. map 中的 key 是唯一的,并且不能修改3. 默认按照小于的方式对 key 进行比较4. map 中的元素如果用迭代器去遍历,可以得到一个有序的序列5. map 的底层为平衡搜索树 ( 红黑树 ) ,查找效率比较高6. 支持 [] 操作符, operator[] 中实际进行插入查找。
1. multiset 是按照特定顺序存储元素的容器,其中元素是可以重复的。2. 在 multiset 中,元素的 value 也会识别它 ( 因为 multiset 中本身存储的就是组成的键值对,因此value 本身就是 key , key 就是 value ,类型为 T). multiset 元素的值不能在容器中进行修改 ( 因为元素总是const 的 ) ,但可以从容器中插入或删除。 3. 在内部, multiset 中的元素总是按照其内部比较规则 ( 类型比较 ) 所指示的特定严格弱排序准则进行排序。4. multiset 容器通过 key 访问单个元素的速度通常比 unordered_multiset 容器慢,但当使用迭代器遍历时会得到一个有序序列。5. multiset 底层结构为二叉搜索树 ( 红黑树 ) 。
其实我们的multiset与set的操作都是一样的,只不过区别是我们的multset是可以接受多个相同元素的,就是少了去重这个功能,仅有排序
1. multiset 中再底层中存储的是的键值对 2. multiset 的插入接口中只需要插入即可3. 与 set 的区别是, multiset 中的元素可以重复, set 是中 value 是唯一的4. 使用迭代器对 multiset 中的元素进行遍历,可以得到有序的序列5. multiset 中的元素不能修改6. 在 multiset 中找某个元素,时间复杂度为logN7. multiset 的作用:可以对元素进行排序
1. Multimaps 是关联式容器,它按照特定的顺序,存储由 key 和 value 映射成的键值对,其中多个键值对之间的key 是可以重复的。 2. 在 multimap 中,通常按照 key 排序和惟一地标识元素,而映射的 value 存储与 key 关联的内容。 key 和value的类型可能不同,通过 multimap 内部的成员类型 value_type 组合在一起, value_type 是组合 key和value 的键值对:typedef pairvalue_type; 3. 在内部, multimap 中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对 key 进行排序的。4. multimap 通过 key 访问单个元素的速度通常比 unordered_multimap 容器慢,但是使用迭代器直接遍历multimap中的元素可以得到关于 key 有序的序列。5. multimap 在底层用二叉搜索树 ( 红黑树 ) 来实现。
multimap 中的接口可以参考 map ,功能都是类似的。注意:1. multimap 中的 key 是可以重复的。2. multimap 中的元素默认将 key 按照小于来比较3. multimap 中没有重载 operator[] 操作 ( 同学们可思考下为什么 ?) 。4. 使用时与 map 包含的头文件相同: