C++14.--初识set与map

关联式容器
键值对
树形结构的关联式容器

关联式容器

在初阶阶段,我们已经接触过 STL 中的部分容器,比如: vector list deque forward_list(C++11) 等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?
关联式容器 也是用来存储数据的,与序列式容器不同的是,其 里面存储的是 <key, value> 结构的键值对,在 数据检索时比序列式容器效率更高

键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量 key value key 代表键值, value 表示与 key 对应的信息 。比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。
template <class T1, class T2>
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 。这四种容器的共同点是:使用平衡搜索树 ( 即红黑树 )作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。

set

set的介绍

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 中存储的是真正的键值对 <key, value> set 中只放 value ,但
在底层实际存放的是由 <value, value> 构成的键值对。
2. set 中插入元素时,只需要插入 value 即可,不需要构造键值对。
3. set 中的元素不可以重复 ( 因此可以使用 set 进行去重 )
4. 使用 set 的迭代器遍历 set 中的元素,可以得到有序序列
5. set 中的元素默认按照小于来比较
6. set 中查找某个元素,时间复杂度为:logN
7. set 中的元素不允许修改 ( 为什么 ?)
8. set 中的底层使用二叉搜索树 ( 红黑树 ) 来实现。

set的使用

set的构造

set的迭代器 set的容量 set修改操作

 我们先来看一下set的遍历

 这时候我们就只能用迭代器去进行遍历了,无法使用[ ]方式,注意:1.范围for也本质是迭代器

2.set这个容器中的数据是不允许被修改的,因为其为一个搜索二叉树,不能修改

set的删除

 这里我们补充一下:我们之前学习别的容器时是没有find的,我们当时使用的是算法中的find,而在这里我们采用的是容器中的find,容器中的find与算法之中的find最大的区别就是时间复杂度不同,算法的为N,这里的为logN,因为本质为树,所以我们尽量使用这里的find,更加高效

map

map的介绍

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的迭代器map的容量与元素访问

map中元素的修改

 map的遍历

map同样也支持遍历,我们这里通过匿名对象创建结点,不过这里因为遍历的值为两个,所以我们将其整合为结构体放入pair键值对中,从pair再去访问数据,而且map也是根据中序遍历默认顺序排序的,其实map与set是很相像的,区别是map中是两个值

 

 事实上在工程中我们更多的是使用make_pair的方式去构造pair对象的,更加的简洁

 operator[ ]

我们通过阅读上面的说明可以得知,这个[ ]其实是一个迭代器,而且与插入函数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<irerator, bool>,也就是一个iterator
		//下一层解引用pair<irerator, bool>,对这个iterator进行解引用,解引用又是一个pair<key_type , mapped_type>
		//最后一层整个对象再取一次second,也就是上层的mapped_type

		//这里不用find而用insert的原因是用find假设map中没有k,无法返回,用insert
		//1.如果k不在map中,则插入有pair<k,mapped_type()>,在返回映射对象的引用
		//2.如果k不在map中,则插入失败,返回k所在节点的中的映射对象的引用
	}

 我们可以看到它的返回值很复杂,拆接下来可以分为3层,内层的insert函数返回的pair的first解引用成外部iterator,而后去调用second返回

我们再来看看用operator[]来进行计数的方式

string strs[] = { "西瓜", "黄瓜", "葡萄", "草莓", "黄瓜", "西瓜", "草莓", "西瓜", "西瓜", "苦瓜", "西瓜", "东瓜", "西瓜" };
	map<string, int>countMap;
	for (auto& str : strs)
	{
		//1.如果水果不在map中,则operator[]会插入pair<str , 0>,返回映射对象(次数)的引用进行了++
		//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::map<std::string, std::string>dict;
	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[] 中实际进行插入查找。

multiset

1. multiset 是按照特定顺序存储元素的容器,其中元素是可以重复的。
2. multiset 中,元素的 value 也会识别它 ( 因为 multiset 中本身存储的就是 <value, value> 组成的键值对,因此value 本身就是 key key 就是 value ,类型为 T). multiset 元素的值不能在容器中进行修改 ( 因为元素总是const ) ,但可以从容器中插入或删除。
3. 在内部, multiset 中的元素总是按照其内部比较规则 ( 类型比较 ) 所指示的特定严格弱排序准则进行排序。
4. multiset 容器通过 key 访问单个元素的速度通常比 unordered_multiset 容器慢,但当使用迭代器遍历时会得到一个有序序列。
5. multiset 底层结构为二叉搜索树 ( 红黑树 )

其实我们的multiset与set的操作都是一样的,只不过区别是我们的multset是可以接受多个相同元素的,就是少了去重这个功能,仅有排序

1. multiset 中再底层中存储的是 <value, value> 的键值对
2. multiset 的插入接口中只需要插入即可
3. set 的区别是, multiset 中的元素可以重复, set 是中 value 是唯一的
4. 使用迭代器对 multiset 中的元素进行遍历,可以得到有序的序列
5. multiset 中的元素不能修改
6. multiset 中找某个元素,时间复杂度为logN
7. multiset 的作用:可以对元素进行排序

multimap

1. Multimaps 是关联式容器,它按照特定的顺序,存储由 key value 映射成的键值对 <key, value> ,其中多个键值对之间的key 是可以重复的。
2. multimap 中,通常按照 key 排序和惟一地标识元素,而映射的 value 存储与 key 关联的内容。 key 和value的类型可能不同,通过 multimap 内部的成员类型 value_type 组合在一起, value_type 是组合 key和value 的键值对
:typedef pair<const Key, T> value_type;
3. 在内部, multimap 中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对 key 进行排序的。
4. multimap 通过 key 访问单个元素的速度通常比 unordered_multimap 容器慢,但是使用迭代器直接遍历multimap中的元素可以得到关于 key 有序的序列。
5. multimap 在底层用二叉搜索树 ( 红黑树 ) 来实现。
multimap map 的唯一不同就是: map 中的 key 是唯一的,而 multimap key 是可以重复的
不过multimap是没有operator[]的,原因是有多个相同的key,不知道返回哪个key的value
multimap 中的接口可以参考 map ,功能都是类似的。
注意:
1. multimap 中的 key 是可以重复的。
2. multimap 中的元素默认将 key 按照小于来比较
3. multimap 中没有重载 operator[] 操作 ( 同学们可思考下为什么 ?)
4. 使用时与 map 包含的头文件相同:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值