c++常用容器总结(学习笔记)

前言:

本篇是对于c++中常用容器的基本实现原理,使用该容器优缺点及部分方法的时间空间复杂度的讲解。

vector容器

底层原理:

std::vector是一种动态数组容器,可以根据需要自动扩展和收缩。它的底层实现主要依赖于一个连续的内存块。

当创建一个空的std::vector时,其底层会分配一块初始大小的内存空间作为存储容器元素的区域。随着向std::vector添加元素,如果超过了当前内存空间的大小,它会重新分配更大的内存块,并将原有元素复制到新分配的内存中

优缺点:

优点:

  1. 高效的随机访问:由于 std::vector 使用连续的内存块来存储元素,因此可以通过下标直接访问元素,具有较高的访问速度。

  2. 动态大小:std::vector 可以根据需要动态增长或缩小,灵活适应不同的数据量需求。但是当后续内存需求超过当前内存碎片大小,则整个容器进行新的地址选择。若一直未找到合适的内存块,则容器会分散存储数据。当删除数据时,内存不会自动删除,以备后续使用

  3. 连续内存分配:由于元素在内存中是连续存储的,所以在某些情况下可以提高 CPU 缓存命中率和性能。

  4. 快速尾部插入和删除:由于 std::vector 内部使用指针管理元素,在末尾进行插入和删除操作非常高效。

缺点:

  1. 插入和删除开销较大:在中间位置插入或删除元素时,需要将后续元素移动到新位置。这可能导致较大的时间开销。不如链表等非连续存储结构的插入

  2. 动态扩展会导致重新分配与拷贝:当 std::vector 的容量不足以容纳新的元素时,会触发重新分配,并将原有元素拷贝到新的内存空间中。这可能导致性能损失。

  3. 不适合频繁插入和删除操作:如果需要频繁进行插入和删除操作,特别是在中间位置,可能不是最优的选择。其他容器如 std::liststd::deque 在这方面更为高效。

综合考虑,在大部分情况下,std::vector 是一个高效、灵活的容器,特别适用于需要随机访问和快速尾部插入/删除的场景。但对于频繁的插入和删除操作,以及需要保证稳定内存地址的需求,则可以考虑其他容器。

set容器和unordered_set容器

set:

底层原理:

std::set 容器是基于红黑树(Red-Black Tree)实现的,它是一种自平衡二叉搜索树。红黑树具有以下特性:

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点是黑色的。
  3. 所有叶子节点(NULL 节点)都是黑色的。
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的。
  5. 对于任意节点到其所有后代叶子节点的简单路径上,经过的黑色节点数量相同。

这些特性确保了红黑树在插入、删除和查找操作时保持平衡,从而提供了较好的性能。

用迭代器访问时,*(迭代器),可以显示出迭代器指向的元素。

优缺点:

优点:

  1. 唯一性:set容器中的元素是唯一的,不会存在重复值。当需要确保集合中没有重复元素时,可以使用set。

  2. 自动排序:set容器中的元素默认按照升序进行排序,这对于需要有序存储数据的场景非常方便。但每次插入和查找数据的时间复杂度为O(log n ),

  3. 快速查找:由于set内部使用了二叉搜索树(红黑树)实现,所以在其中查找特定元素的效率很高。

  4. 插入和删除效率较高:与向数组或链表插入或删除元素相比,向set容器插入或删除元素通常具有较好的性能,这是因为内部的存储结构并非为连续的,删除插入元素并不会影响其他元素。

缺点:

不支持随机访问(即没有下标访问),内存开销较大。插入和查找数据的时间复杂度为O(log n )。只能以迭代器访问元素。

unordered_set :

底层原理:

std::unordered_set 容器则是使用哈希表(Hash Table)实现的。哈希表通过将元素与其对应的哈希值进行关联来存储和检索数据。内部使用数组来保存元素,并使用散列函数将键转换为数组索引。如果多个键具有相同的散列值,则使用链表或其他方法处理冲突。

哈希表具有快速的插入、删除和查找操作,平均情况下具有常数时间复杂度。然而,在最坏情况下,哈希表可能会出现碰撞(collision),导致性能下降并增加搜索时间。

总结起来,std::set 使用红黑树实现,保持有序性,而 std::unordered_set 则使用哈希表实现,提供更快的插入、删除和查找操作,但不保持有序。

优缺点:

优点:

  1. 快速的查找:由于使用了哈希表,unordered_set具有快速的查找性能,平均时间复杂度为O(1)。
  2. 高效的插入和删除操作:无序集合支持高效地进行元素的插入和删除操作,平均时间复杂度也为O(1)。
  3. 不重复性:unordered_set确保存储的元素不会重复。

缺点:

  1. 无序性:作为一个无序容器,unordered_set中元素没有特定的顺序。如果需要按照一定顺序遍历或访问元素,则不适合使用该容器。
  2. 内存占用较大:相比于有序容器,unordered_set通常需要更多的内存空间来维护哈希表结构。
  3. 哈希冲突:由于使用哈希表,可能会出现不同元素映射到相同位置(哈希冲突)的情况。当哈希冲突较多时,查询性能可能会降低。

map和unordered_map容器

map容器:

底层原理:

map容器是C++ STL中的关联容器之一,它实现了键值对(key-value)的存储和快速查找。其内部实现基于红黑树数据结构。map容器的键在默认情况下按递增排序。值不进行排序。

优缺点:

优点:

  1. 有序性:map会按照键的顺序进行排序,这样可以方便地进行范围查找或遍历操作。
  2. 动态性:map容器支持动态插入和删除元素,可以在运行时根据需要进行修改。
  3. 对于特定的规则的查找(例如大于或小于某些数字)的时间复杂度为log(n)。

缺点:

  1. 内存开销:相比于其他数据结构如vector或array,map占用的内存空间较大。因为它需要额外保存键值对之间的关联信息。
  2. 速度略慢:与数组或哈希表相比,在插入和访问元素时,由于红黑树维护有序性的额外开销,map容器可能稍微慢一些。
  3. 迭代器失效:当插入或删除元素时,迭代器可能会失效。这意味着在循环遍历过程中做改变可能导致不可预料的结果。
  4. 插入和删除:由于map内部是基于红黑树实现的,删除和插入操作的时间复杂度(O(log n))。

unordered_map:

底层实现:

哈希表。哈希函数:首先,对于每个键(Key),使用哈希函数将其转换为一个整数值。这个整数值就作为该键在哈希表中的位置索引。

桶结构:哈希表内部由多个桶(buckets)组成,每个桶可以存储一个或多个键值对。通过哈希函数计算得到的位置索引决定了具体放入哪个桶中。

容器特点:

优点:

  1. 快速的插入和查找:unordered_map 使用哈希表实现,具有常数时间复杂度(O(1))的插入和查找操作,相对于其他关联容器来说速度更快。和数组的查询时间一致,和链表插入时间一致
  2. 灵活性:unordered_map 可以存储任意类型的键值对,并且支持自定义的哈希函数和比较函数。
  3. 内存效率:由于使用哈希表实现,unordered_map 可以动态地调整桶的大小,节省内存空间。

缺点:

  1. 无序性:由于 unordered_map 是基于哈希表实现的,其元素在内部是无序存储的。这在某些应用场景下可能不符合需求。
  2. 迭代顺序不确定性:由于无序性,unordered_map 的迭代顺序是不确定的,即使元素没有改变也可能导致遍历结果不同。
  3. 哈希冲突影响性能:当哈希冲突发生时,需要额外的链表或红黑树来处理冲突。这可能导致一些操作在最坏情况下时间复杂度达到 O(n),尽管平均情况下仍然是常数时间复杂度。

list容器

实现原理:

数据结构:list 是由一个个节点组成的双向链表,每个节点包含两个指针,分别指向前一个节点和后一个节点。这种数据结构使得在 list 中插入、删除元素时效率很高。

  1. 迭代器:list 提供了双向迭代器(bidirectional iterator),可以在容器内部进行遍历操作。

优缺点:

  1. 插入操作:在 list 中插入元素时,只需调整相邻节点的指针即可,不需要移动其他元素。因此,在任何位置插入或删除元素都是常数时间复杂度(O(1))。

  2. 删除操作:同样地,删除元素只需调整相邻节点的指针即可完成。与数组不同,在 list 中删除元素不会引起其他元素的移动。

  3. 空间分配:当有新的元素被插入时,list 动态地分配新的内存空间来保存新节点,并自动处理内存管理。这使得 list 可以灵活地增长或缩小。并且由于list并非是连续存储结构

  4. 元素存储和访问:每个节点中存储着实际的元素值,可以通过迭代器进行访问和修改。

总体而言,list 采用双向链表作为底层数据结构实现,在插入、删除元素时具有较高的效率。但由于链表节点需要额外的指针来维护连接关系,会占用更多的内存空间,并且不支持随机访问,因此在需要频繁随机访问元素或对内存占用有严格要求的情况下,可能不是最优选择。

queue容器:

底层原理:

queue容器是堆的存储结构,遵循先进先出:即最早进入队列的元素首先被移除。容器只支持对两端进行操作。

底层容器选择:默认情况下,queue 使用 deque 作为底层容器。deque 是一个双端队列,支持在两端进行元素的插入和删除操作。

优缺点:

  1. 入队操作:当向 queue 中插入元素时,会调用底层容器的 push_back() 方法,在 deque 的尾部插入新元素。时间复杂度为o(1)

  2. 出队操作:当从 queue 中取出元素时,会调用底层容器的 front() 方法获取队头元素,并调用 pop_front() 方法将其移除。

  3. 大小统计:可以使用 size() 方法获取 queue 中当前存储的元素个数。

适用场景:

  1. 消息队列:在异步处理任务时,可以将任务添加到队列中,然后按照添加顺序依次处理。

  2. 广度优先搜索:在图或树等数据结构的广度优先搜索算法中,使用队列来存储待访问的节点,以确保按层级顺序进行遍历。

  3. 缓存管理:当需要缓存最近使用的元素时,可以使用队列来维护缓存,并通过出队操作来移除最旧的元素。

  4. 请求调度:在多线程或多进程环境中,可以使用队列作为任务调度器,将请求添加到队列中,并由工作线程或进程按照顺序进行处理。

stack栈容器:

实现原理:

使用 std::deque 作为其底层容器。也可以通过指定不同的底层容器类型来创建不同类型的栈,比如使用 vectorlist。无论使用哪种底层容器,栈的基本操作都是通过调用相应底层容器提供的操作来完成。栈容器的特点是先进后出,且只能在头部进行操作。

常用函数:

  • push():将元素添加到底层容器的末尾。
  • pop():移除底层容器末尾的元素。
  • top():访问并返回底层容器末尾的元素。
  • empty():检查底层容器是否为空。
  • size():获取底层容器中元素的数量。

由于容器特征,函数操作的时间复杂度都为O(1)。

学习链接:https://xxetb.xetslk.com/s/3nlW3Q

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值