STL中各类容器详细介绍

STL介绍

STL(Standard Template Library),即标准模板库,是一个具有工业强度的,高效的C++程序库。它被容纳于C++标准程序库(C++ Standard Library)中,是ANSI/ISO C++标准中最新的也是极具革命性的一部分。该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法。为广大C++程序员们提供了一个可扩展的应用框架,高度体现了软件的可复用性。

C++STL提供的数据结构

1. Sequence Containers:维持顺序的容器。

(a). vector:

动态数组,在堆中分配内存,是我们最常使用的数据结构之一。

特点:

  • 底层结构 : 底层由 动态数组 实现 , 特点是 存储空间 连续 ;
  • 访问遍历 : 支持 随机访问迭代器 , 可使用下标访问 , 访问元素非常快 O(1) 复杂度 ;
  • 插入 / 删除 : 尾部插入 / 删除效率高 O(1) 复杂度 ; 中间 和 头部插入/删除效率低 , 由于存储空间连续 , 需要将插入 / 删除位置之后的元素依次改变位置 , O(n) 复杂度 ;
  • 空间效率 : 底层实现时 , 会事先预留一些额外空间 , 以减少重新分配的次数 ;
  • 使用场景 : 需要 随机访问 且 频繁在尾部进行操作 的场景 ; 如果频繁增删元素 则 不适用该容器 ;

时间复杂度:

实现原理:

简单理解,就是vector是利用上述三个指针来表示的,基本示意图如下:
在这里插入图片描述
两个关键大小:
大小:size=_Mylast - _Myfirst;
容量:capacity=_Myend - _Myfirst;
分别对应于resize()、reserve()两个函数。


size表示vector中已有元素的个数,capacity表示vector最多可存储的元素的个数;

为了降低二次分配时的成本,vector实际配置的内存空间大小会比客户需求的更大一些,以备将来扩充,这就是capacity的概念。即capacity>=size,当等于时,容器此时已满,若再要加入新的元素时,就要重新进行内存分配,整个vector的数据都要移动到新内存。

vector:扩容机制:

vector 容器扩容的过程需要经历以下 3 步:

1. 完全弃用现有的内存空间,重新申请更大的内存空间;

2. 将旧内存空间中的数据,按原有顺序移动到新的内存空间中;

3. 最后将旧的内存空间释放。

因为 vector 扩容需要申请新的空间,所以扩容以后它的内存地址会发生改变。vector 扩容是非常耗时的,为了降低再次分配内存空间时的成本,每次扩容时 vector 都会申请比用户需求量更多的内存空间(这也就是 vector 容量的由来,即 capacity>=size),以便后期使用。VS里面扩容机制是1.5倍扩容,gcc里面是2倍扩容。


(b). list:

双向链表,也可以当作 stack 和 queue 来使用。有个缺点,链表不支持快速随机读取。

特点

  • 底层结构 : 底层由 双向链表 实现 , 特点是 存储空间 不连续 ;
  • 访问遍历 : 不支持 随机访问迭代器 , 只能通过迭代器进行访问 ;
  • 插入 / 删除 : 任意位置 插入 / 删除 效率都很高 ;
  • 空间效率 : 每个元素 都需要 分配额外的空间 , 存储 当前元素的 前驱元素 和 后继元素 ;
  • 使用场景 : 需要 在任意位置 频繁 插入 / 删除 操作的 场景 ;

时间复杂度:


(c). deque:

双端队列, 是一种优化了的、对序列两端元素进行添加和删除操作的基本序列容器。它允许较为快速地随机访问,但它不像vector 把所有的对象保存在一块连续的内存块,而是采用多个连续的存储块,并且在一个映射结构中保存对这些块及其顺序的跟踪。向deque 两端添加或删除元素的开销很小。它不需要重新分配空间,所以在两端端增删元素比vector 更有效。

特点:

  • 底层结构 : 底层由 双向队列 实现 , 特点是 存储空间 连续 ;
  • 访问遍历 : 支持 随机访问迭代器 , 其性能比 vector 动态素组要低 ;
  • 插入 / 删除 : 头部 和 尾部 插入 / 删除效率高 , O(1) 复杂度 ; 中间 插入/删除效率低 , 由于存储空间连续 , 需要将插入 / 删除位置之后的元素依次改变位置 , 比 vector 动态数组要快一些 ;
  • 空间效率 : 底层实现时比 vector 的结构要复杂 , 也会事先预留一些额外空间 , 以减少重新分配的次数 ;
  • 使用场景 : 需要 随机访问 且 频繁在 首部 和 尾部 进行操作 的场景 ; 如果频繁 在中部 增删元素 则 不适用该容器 ;

实现原理:

deque 是由一段一段的定量的连续空间构成。一旦有必要在 deque 前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在 deque 的头端或者尾端。deque 最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。
既然 deque 是分段连续内存空间,那么就必须有中央控制,维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐。

deque 采取一块所谓的 map(不是 STL 的 map 容器)作为主控,这里所谓的 map 是一小块连续的内存空间,其中每一个元素(此处成为一个结点)都是一个指针,指向另一段连续性内存空间,称作缓冲区。缓冲区才是 deque的存储空间的主体。

时间复杂度:


(d). array:

固定大小的数组,一般在刷题时我们不使用。


(e). forward_list:

单向链表,一般在刷题时我们不使用。


2. Container Adaptors:容器适配器。

(a). stack:

后入先出(LIFO)的数据结构,默认基于 deque 实现。stack 常用于深度优先搜
索、一些字符串匹配问题以及单调栈问题。


(b). queue:

先入先出(FIFO)的数据结构,默认基于 deque 实现。queue 常用于广度优先
搜索。


(c). priority_queue:

最大值先出的数据结构,默认基于vector实现堆结构。它可以在O(n log n)
的时间排序数组,O(log n) 的时间插入任意值,O(1) 的时间获得最大值,O(log n) 的时
间删除最大值。priority_queue 常用于维护数据结构并快速获取最大或最小值。


3. Associative Containers:有序关联容器。

(a). set:

有序集合,元素不可重复,底层实现默认为红黑树,它可以在 O(n log n) 的时间排序数组,O(log n) 的时间插入、删除、查找任何节点。这里注意,set 和 priority_queue 都可以用
于维护数据结构并快速获取最大最小值,但是它们的时间复杂度和功能略有区别,如
priority_queue 默认不支持删除任意值,而 set 获得最大或最小值的时间复杂度略高,具
体使用哪个根据需求而定。

特点:

  • 底层结构 : 底层由 红黑树 实现 , 红黑树 是 一种 平衡二叉搜索树 , 存储空间 不连续 ;
  • 访问遍历 : 不支持 随机访问迭代器 , 不能通过过下标访问 , 只能通过迭代器进行访问 ;
  • 插入 / 删除 : 查询 / 插入 / 删除 效率 为 O(log n) 复杂度 ;
  • 排序方式 : 默认使用 less 仿函数 , 即 < 运算符进行排序 ; 也可以自定义 排序规则 仿函数 ;
  • 使用场景 : 需要 有序集合 且 元素 不重复 的场景 ;

时间复杂度:

增删改查都近似 = O(log N)


(b). multiset:

支持重复元素的 set。

(c). map:

有序映射或有序表,在 set 的基础上加上映射关系,可以对每个元素 key 存一个
值 value。

特点:

  • 底层结构 : 底层由 红黑树 实现 , 红黑树 是 一种 平衡二叉搜索树 , 存储空间 不连续 ; 存储的 元素 是 键值对 元素 ;
  • 访问遍历 : 不支持 随机访问迭代器 , 不能听过下标访问 , 只能通过迭代器进行访问 ;
  • 插入 / 删除 : 查询 / 插入 / 删除 效率 为 O(log n) 复杂度 ; 与 set 集合容器相同 ;
  • 排序方式 : 默认使用 less 仿函数 , 即 < 运算符进行排序 ; 也可以自定义 排序规则 仿函数 ; map 映射容器 不允许重复的键 , multimap 多重映射容器允许重复的键 ;
  • 使用场景 : 需要 有序 键值对 且 元素 不重复 的场景 ;
std::map 映射容器 与 std::set 集合容器 的区别是
map 容器存储的是 键值对 元素 , 元素是 pair  
set 容器 存储的是 单纯的 键 单个元素 ;

时间复杂度:

增删改查都近似 = O(log N)


(d). multimap:

支持重复元素的 map。


4. Unordered Associative Containers:无序关联容器

(a). unordered_set:

哈希集合,可以在 O(1) 的时间快速插入、查找、删除元素,常用于快
速的查询一个元素是否在这个容器内。


(b). unordered_multiset:

支持重复元素的 unordered_set。


(c). unordered_map:

哈希映射或哈希表,在 unordered_set 的基础上加上映射关系,可以对
每一个元素 key 存一个值 value。在某些情况下,如果 key 的范围已知且较小,我们也
可以用 vector 代替 unordered_map,用位置表示 key,用每个位置的值表示 value。

实现原理

unordered_map 容器和 map 容器一样,以键值对(pair类型)的形式存储数据,存储的各个键值对的键互不相同且不允许被修改。但由于 unordered_map 容器底层采用的是哈希表存储结构,该结构本身不具有对数据的排序功能,所以此容器内部不会自行对存储的键值对进行排序。底层采用哈希表实现无序容器时,会将所有数据存储到一整块连续的内存空间中,并且当数据存储位置发生冲突时,解决方法选用的是“链地址法”(又称“开链法”)。整个存储结构如下图(其中,Pi 表示存储的各个键值对):

可以看到,当使用无序容器存储键值对时,会先申请一整块连续的存储空间,但此空间并不用来直接存储键值对,而是存储各个链表的头指针,各键值对真正的存储位置是各个链表的节点。

为什么会出现哈希冲突:

由于通过哈希函数产生的哈希值是有限的,而数据可能比较多,导致经过哈希函数处理后仍然有不同的数据对应相同的值,这时候就产生了哈希冲突。

哈希冲突具体解决方法:

在正式讲解hash冲突之前,先介绍一个术语:在hashtable中,数组的每一个元素叫做桶(bucket)。

为了解决hash冲突,hashtable在每个桶里bucket[index]不再直接存储待插入节点的值,而是存储一个哨兵节点,使其指向一个链表,由这个链表来存储每次插入节点的值:

•桶的索引值index依然是bucket_index函数的计算方式,即通过待插入节点的键来获取•待插入节点的值在哨兵指向的链表头部插入,由于是头部插入整个插入过程还是O(1)时间复杂度。

当发生hash冲突时,将所有hashcode相同的节点都插入到同一个链表中,如图1所示。由于采用的是头部插入法,那么即便是发生了hash冲突,此时插入时间复杂度也依然是O(1)。

图片

图1  hash冲突的解决方法

上述解决hash冲突的方法,叫做开链法。此时,hash冲突的问题似乎解决了,能够插入多个hashcode一样的节点,并且插入操作的时间复杂度仍然是O(1)。

但是!!!,当出现严重的hash冲突,会造成bucket[idx]指向的链表节点很长,此时搜索和删除一个节点的时间复杂度最坏却可能变成O(N),即哈希表已经退化成链表,那么就违背了一开始设计hashtable的初衷,即弥补数组O(N)的搜索、删除时间复杂度。

 hash退化

 负载因子

为了解决hash退化,引入了两个概念:

•负载因子(load_factor),是hashtable的元素个数与hashtable的桶数之间比值;•最大负载因子(max_load_factor),是负载因子的上限

他们之间要满足:

load_factor = map.size() / map.buck_count() // load_factor 计算方式load_factor <= max_load_factor              // 限制条件

当hashtable中的元素个数与桶数比值load_factor >= max_load_factor时,hashtable就会自动增加桶数,并重新进行哈希扩容,并重新插入,来降低load_factor。

哈希扩容:即使分配一块更大内存,来容纳更多的桶。

重新插入:按照上述插入步骤将原来桶中的buck_size个节点重新插入到新的桶中。

Rehash后,桶数增加了而元素个数不变,再次满足load_factor <= max_load_factor条件。

 Rehash

hashtable,由于要一直满足 load_factor <= max_load_factor ,限制着hash冲突程度,即每个桶的链表节点数不会无限制增加,整个hashtable的节点数达到一定程度就会Rehash,确保hashtable的搜索、删除的平均时间复杂度还是O(1)。

在unordered_map有个rehash函数,可以在任意时候使unordered_map发生Rehash,也可以等load_factor > max_load_factor时自动发生。注意:

•不同编译器的扩容策略不同,因此不编译器Rehash后桶的个数不一致很正常。•目前msvc和g++中的max_load_factor字段默认值都是1。


(d). unordered_multimap:

支持重复元素的 unordered_map。

STL 各容器使用场景示例

  • 如果需要 随机访问 , 则使用 vector 单端数组 或 deque 双端数组 容器 ;
  • 如果 需要 在 尾部 频繁 插入 / 删除 , 则使用 vector 单端数组 ;
  • 如果 需要 在 首部 和 尾部 频繁 插入 / 删除 , 则使用 deque 双端数组 ;
  • 如果 需要 在 任意位置 频繁 插入 / 删除 , 则使用 list 双向链表 ;
  • 如果需要保持 元素 有序 且 不重复 , 则使用 set 集合容器 ;
  • 如果需要保持 元素 有序 且 可重复 , 则使用 multiset 多重集合容器 ;
  • 如果不需要保持 元素 有序 , 可使用unordered_set, unordered_map;


————————————————
引用:

https://blog.csdn.net/zuihaobizui/article/details/119741156

https://blog.csdn.net/shulianghan/article/details/135363350

https://www.cnblogs.com/yinbiao/p/11636405.html

C++ vector的内部实现原理及基本用法_vector内部实现-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/795318.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端学习<四>JavaScript基础——06-基本数据类型:String 和 Boolean

今天这篇文章&#xff0c;我们详细讲一下基本数据类型。 String 字符串 语法 字符串型可以是引号中的任意文本&#xff0c;其语法为&#xff1a;双引号 "" 或者单引号 。 来看个示例。下面的这些&#xff0c;都是字符串&#xff1a; var a abcde;var b 千古壹号…

Photoshop 2024 中文---专业图像处理软件的又一次飞跃

Photoshop 2024是一款功能强大的图像处理软件&#xff0c;广泛应用于创意设计和图像处理领域。它提供了丰富的绘画和编辑工具&#xff0c;包括画笔、铅笔、颜色替换、混合器画笔等&#xff0c;使用户能够轻松进行图片编辑、合成、校色、抠图等操作&#xff0c;实现各种视觉效果…

云备份day03

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C云备份项目 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 主要内容介绍了第三方库httplib的一些内容&#xff0c;以及实现…

Java 进程状态

一&#xff0c;进程介绍 定义&#xff1a;进程是计算机中运行中的程序的实例。它包含了程序的代码、数据以及程序运行时所需的各种资源&#xff0c;如内存空间、CPU时间等。 特征&#xff1a; 动态性&#xff1a;进程是动态创建、执行和销毁的。并发性&#xff1a;在多道程序环…

VSCode好用插件

由于现在还是使用vue2&#xff0c;所以本文只记录vue2开发中好用的插件。 美化类插件不介绍了&#xff0c;那些貌似对生产力起不到什么大的帮助&#xff0c;纯粹的“唯心主义”罢了&#xff0c;但是如果你有兴趣的话可以查看上一篇博客&#xff1a;VSCode美化 1. vuter 简介&…

【opencv】示例-barcode.cpp 条形码检测和解码

#include <iostream> // 引入标准输入输出流库 #include "opencv2/objdetect.hpp" // 引入OpenCV物体检测库 #include "opencv2/imgproc.hpp" // 引入OpenCV图像处理库 #include "opencv2/highgui.hpp" // 引入OpenCV高层GUI库using names…

169.乐理基础-调式板块总结、调式判断

如果到这五线谱还没记住还不认识的话去看102.五线谱-高音谱号与103.五线谱-低音谱号这两个里&#xff0c;这里面有五线谱对应的音名&#xff0c;对比着看 如果不认识调号去看112.五线谱的调号&#xff08;一&#xff09;、113.五线谱的调号&#xff08;二&#xff09;、114.快…

如何使用 Midjourney?2024年最新更新

一&#xff1a;基础篇 1&#xff1a;注册 首先&#xff0c;你需要注册一个 Discord 账号&#xff0c;然后加入 Midjourney 的 Discord 服务器。或者去 Midjourney 的官网点击右下角的 Join the Beta&#xff1a; ​ 2&#xff1a;在 Discord 公共服务器里使用 注册并进入到…

JVM基础

初识JAM JVM就是JAVA虚拟机&#xff0c;本质上是一个运行在计算机上的程序&#xff0c;他的职责是运行JAVA字节码文件. 下面是java代码执行过程 JVM的功能 1.解释和运行 对字节码文件中的指令实时的解释成机器码 2.内存管理 自动为对象&#xff0c;方法等分配内存自动的垃圾回…

基于顺序表实现通讯管理系统!(有完整源码!)

​​​​​​​ 个人主页&#xff1a;秋风起&#xff0c;再归来~ 文章专栏&#xff1a;C语言实战项目 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克心守己&#xff0c;律己则安&#xff01;​​​​​​​ 目录 1、实现思路 ​…

OCm (Radeon Open Compute) 和 CUDA (Compute Unified Device Architecture)

OCm&#xff08;Radeon Open Compute&#xff09;和CUDA&#xff08;Compute Unified Device Architecture&#xff09;是两种旨在利用图形处理单元&#xff08;GPU&#xff09;进行通用计算的技术和框架。 OCm&#xff08;Radeon Open Compute&#xff09;&#xff1a; OCm&…

CentOS 7 下离线安装RabbitMQ教程

CentOS 7 下安装RabbitMQ教程一、做准备&#xff08;VMWare 虚拟机上的 CentOS 7 镜像 上安装的&#xff09; &#xff08;1&#xff09;准备RabbitMQ的安装包&#xff08;rabbitmq-server-3.8.5-1.el7.noarch&#xff09;下载地址mq https://github.com/rabbitmq/rabbitmq-se…

os.listdir()bug总结

今天测试出一个神奇的bug&#xff0c;算是教训吧&#xff0c;找了两天不知道问题在哪&#xff0c;最后才发现问题出现在这 原始文件夹显示 os.listdir()结果乱序 import os base_path "./file/"files os.listdir(base_path)print(files)问题原因 解决办法(排序)

NB-IOT 介绍 1

1 名称介绍 NB-----Narrow Band IOT -----Internet of things NB-IOT---窄带物联网 2 物联网技术发展 以太网&#xff1a;网线 RS232一种串行通信标准&#xff0c;通常采用正负电压来表示逻辑值&#xff0c;如正电压表示逻辑1&#xff0c;负电压表示逻辑0。 RS485一种串行通…

(学习日记)2024.04.06:UCOSIII第三十四节:互斥量函数接口讲解

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

【二分查找】Leetcode 二分查找

题目解析 二分查找在数组有序可以使用&#xff0c;也可以在数组无序的时候使用&#xff08;只要数组中的一些规律适用于二分即可&#xff09; 704. 二分查找 算法讲解 当left > right的时候&#xff0c;我们循环结束&#xff0c;但是当left和right缩成一个点的时候&#x…

大数据分析与内存计算——Spark安装以及Hadoop操作——注意事项

一、Spark安装 1.相关链接 Spark安装和编程实践&#xff08;Spark3.4.0&#xff09;_厦大数据库实验室博客 (xmu.edu.cn) 2.安装Spark&#xff08;Local模式&#xff09; 按照文章中的步骤安装即可 遇到问题&#xff1a;xshell以及xftp不能使用 解决办法&#xff1a; 在…

Node.js------Express

◆ 能够使用 express.static( ) 快速托管静态资源◆ 能够使用 express 路由精简项目结构◆ 能够使用常见的 express 中间件◆ 能够使用 express 创建API接口◆ 能够在 express 中启用cors跨域资源共享 一.初识Express 1.Express 简介 官方给出的概念&#xff1a;Express 是基…

AcWing 312. 乌龟棋(每日一题)

原题链接&#xff1a;312. 乌龟棋 - AcWing题库 小明过生日的时候&#xff0c;爸爸送给他一副乌龟棋当作礼物。 乌龟棋的棋盘只有一行&#xff0c;该行有 N 个格子&#xff0c;每个格子上一个分数&#xff08;非负整数&#xff09;。 棋盘第 1 格是唯一的起点&#xff0c;第…

LC 222.完全二叉树的节点个数

222. 完全二叉树的节点个数 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二叉树 的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都达到最大值&#xff0c;并且最下面一层的节点都集中…