学习STL的历程——关联容器

这篇博客介绍了STL中的关联容器,包括set、map、multiset、multimap、unordered_set和unordered_map。关联容器存储键值对,其中set和map基于红黑树,查询效率为O(logN),元素自动排序且键唯一。unordered_set和unordered_map使用哈希表,查找、插入、删除近似O(1),但无序。文章还讨论了哈希冲突的解决方法以及有序和无序容器的优缺点和适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

STL模板库包括了容器、算法、迭代器、仿函数、适配器、分配器。

主要谈谈容器

STL中的容器有序列容器和关联容器,容器适配器等

1)序列容器(以线性序列的方式存储元素,没对元素进行排序,元素的顺序和存储它们的顺序相同)

主要包括 vector string list deque

2)关联容器(存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高

主要包括 set map multiset multimap

3)适配容器 就是由基本的容器适配(改造)出来的那些容器

主要包括 queue stack priority_queue

这边主要谈谈关联容器

Set和Map底层都为红黑树,查询效率为O(logN),一个set和map关键字(key)必须唯一,但值可以不同(对于map,因为set键和值相同),自动排序。

拓展下红黑树可以认为是平衡的二叉查找树,只要不平衡就会调整,不会出现二叉查找树瘸脚导致效率低下的情况。

图为:二叉查找树瘸脚情况

set的特性是,所有元素都会根据元素的键值自动排序,set的元素不像map那样可以同时拥有实值(value)和键值(key),set元素的键值就是实值,实值就是键值。set不允许两个元素有相同的键值。

代码:

set<int>s;
s.insert(7);
s.insert(2);
s.insert(3);
s.insert(4);
s.insert(1);
s.insert(6);
s.insert(5);
for (auto x:s)
{
    cout << x << endl;
}

这个测试说明了set容器能够自动排序(默认从小到大),set无法使用[ ],输出得借助迭代器

for (it = s.begin(); it != s.end();it++)
	{
		cout << *it << endl;
	}

插入两个相同的键

s.insert(7);
s.insert(7);

第二个不会起作用,在set和map中只允许一个键

Map中元素的键是唯一的,如果键已经存在,用insert插入相同的键的话不起作用,而如果用数组[key]=value的形式则改变其中的值

创建map的代码:

map<int, string>m;
m.insert(pair<int, string>(10, "bbb"));//10为键, bbb为值
m.insert(make_pair(11, "ccc"));
m.insert({ 9, "aaa" });
m[12] = "ddd";//12为键,ddd为值
for (auto x:m)
{
	cout << x.first <<": "<< x.second<<endl;
}

和set一样,如果插入相同的键值,后插入的不起作用,例如:

m.insert(pair<int, string>(10, "bbb"));//10为键, bbb为值
m.insert(make_pair(10, "ccc"));

这两个的键都为10,下面的不起作用;

但如果用[key]=value的方式则可以改变键值对的值

m.insert(pair<int, string>(10, "bbb"));//10为键, bbb为值
m[10] = "ddd";//10为键,ddd为值

这样得到的值将为”ddd”

同样输出借用迭代器

map<int, string>m;
map<int, string>::iterator it;
m.insert(pair<int, string>(10, "bbb"));//10为键, bbb为值
m.insert(make_pair(10, "ccc"));
m.insert({ 9, "aaa" });
m[10] = "ddd";//10为键,ddd为值
for (it = m.begin(); it != m.end();it++)
{
	cout << it->first << ": " << it->second << endl;
}

Map中的元素是自动按key升序排序,所以不能对mapsort函数

Set 与map的count(key)计数得到的要么是1,要么是0;因为不能有重复的键。

对于multisetmultimap不同的地方在于,它们允许多个键存在,而下标操作符[ ]无法再使用,因为对于相同键值无法确定要找的元素是哪个,不再详细说明。

再谈谈unordered_setunordered_map

这个unorder暗示着无序,不按顺序存储

这两个都是根据哈希表也叫散列表(hash_table)实现的,根据keyvalue直接进行访问。利用数组的特点,寻址容易,利用key值直接算出元素位置。

优点:不论哈希表中有多少数据,它的查找,插入,删除所需要的时间均接近于O(1)

缺点:基于数组,创建之后难以扩展,在填满时性能会下降。

再提一句哈希冲突(即:经过哈希映射后,得到的值一样)理解为同个数组下标有两个元素

如何解决呢?

【1】开放寻址法(既然位置被占了,那就找个新的位置看看是否可以用,直到找到空位就将元素放入)

【2】拉链法(用指针连接链表挂起来)

回归正传:

包含头文件#include < unordered_set >

在一个unordered_set内部,元素不会按任何顺序排序用法和set类似但底层不同!

insert() 向容器中添加新元素。

emplace() 向容器中添加新元素,效率比 insert() 方法高。

注意,此容器模板类中没有重载 [ ] 运算符,也没有提供 at() 成员方法。不仅如此,由于 unordered_set 容器内部存储的元素值不能被修改,因此无论使用那个迭代器方法获得的迭代器,都不能用于修改容器中元素的值。

 包含头文件#include < unordered_map >

在一个unordered_map内部,元素不会按任何顺序排序用法和map类似但底层不同!

那unordered_set和unordered_map能够使用sort排序吗?既然已经放在无序容器内为啥还要排序呢?如果实在需要排序,可以借助vector等实现,但是需要写比较仿函数

所以能使用sort进行排序的标准容器为vector, list, deque, string序列容器,而关联容器本身有序,无序关联容器不能使用

总结下有序容器与无序容器:

优缺点以及适用处(这边以map/unordered_map:作总结)

map:

优点:

有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作

红黑树,内部实现一个红黑树使得map的很多操作在O(logn)的时间复杂度下就可以实现,因此效率非常的高

缺点: 空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质,使得每一个节点都占用大量的空间

适用处:对于那些有顺序要求的问题,用map会更高效一些

unordered_map:

优点: 因为内部实现了哈希表,因此其查找速度非常的快O(1)

缺点: 哈希表的建立比较耗费时间

适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map

总结:

内存占有率的问题就转化成红黑树 VS hash表 , 还是unordered_map占用的内存要高。

但是unordered_map执行效率要比map高很多

对于unordered_map或unordered_set容器,其遍历顺序与创建该容器时输入的顺序不一定相同,因为遍历是按照哈希表从前往后依次遍历的

map和unordered_map的使用

unordered_map的用法和map是一样的,提供了 insert,size,count等操作,并且里面的元素也是以pair类型来储存的。其底层实现是完全不同的,上方已经解释了,但是就外部使用来说却是一致的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LV小猪精

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值