vector、list、deque、priority_queue、multiset、unordered_map、哈希表的底层实现原理?什么时候使用vector、list以及deque?

本文详细介绍了C++STL中几种常见容器——vector、list和deque的底层实现原理,包括它们的内存管理和操作性能特点。vector基于动态数组,适合尾部操作;list采用双向链表,适合任意位置插入删除;deque结合了数组和链表的优点,适用于两端操作。此外,还提及了priority_queue的堆实现和multiset的红黑树实现。

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

一、vector底层实现原理?

在 C++ STL 中,vector 是一个动态数组,它的底层实现是基于连续的内存空间,具有以下特点:

1. 连续内存空间:vector 内部存储元素的空间是连续的,这意味着可以通过指针运算随机访问任何元素,也可以高效地遍历整个数组。

2. 动态扩容:当元素数量超过 vector 内部存储空间时,vector 会重新分配内存空间,并将所有元素复制到新的空间中。为了避免频繁的扩容操作,vector 会在内部维护一个容量(capacity)变量,表示当前可用的内部存储空间大小,如果超过了容量,就需要重新分配更大的内存空间。

3. 可变大小:由于 vector 内部存储空间是动态的,因此可以根据需要随时添加或删除元素,而不需要手动管理内存空间。当元素数量减少时,vector 也会自动回收多余的内存空间。

4. 随机访问:由于 vector 内部存储元素的空间是连续的,因此可以通过指针运算实现随机访问,也可以使用迭代器实现迭代遍历。

在 vector 中,元素的插入和删除操作比较耗时,因为需要重新分配内存空间,并复制所有元素。因此,建议在需要频繁插入和删除元素的情况下,使用其他 STL 容器,例如 list、deque 等。

二、list底层实现原理?

在 C++ STL 中,list 是一个双向链表,它的底层实现是基于节点(node),每个节点存储一个元素,同时包含指向前驱节点和后继节点的指针。具有以下特点:

1. 双向链表:list 内部存储元素的节点是双向链表,每个节点包含了指向前驱节点和后继节点的指针,因此可以在常数时间内实现任意位置的元素插入、删除和访问

2. 动态内存分配:当元素数量增加时,list 会自动分配新的节点内存,不需要重新分配整个链表的内存空间,因此插入和删除操作非常高效。

3. 无需移动元素:由于 list 内部存储元素的节点是双向链表,因此插入和删除操作只需要调整相邻节点的指针,不需要移动其他元素,因此对于非连续存储的元素,list 的插入和删除操作比 vector 更高效。

4. 不支持随机访问:由于 list 内部存储元素的节点是链表,因此不能通过指针运算实现随机访问,只能通过迭代器实现遍历访问。

需要注意的是,由于 list 是基于节点实现的,每个节点需要额外的指针空间存储前驱节点和后继节点的地址信息,因此在存储大量数据时,list 的空间占用量会比 vector 更大。此外,list 也不支持随机访问,因此在需要随机访问元素的情况下,应该选择 vector 等其他 STL 容器。

三、deque的底层实现原理?

`std::deque`(双端队列)是一个 C++ STL 容器,它是一个双向开口的序列容器,可以在两端进行高效地插入和删除操作。与 `std::vector` 不同,`std::deque` 不要求它的元素在内存中是连续存储的。这是因为 `std::deque` 的实现使用了一个双向链表的数组,也就是一个类似于“分块”的数据结构。

具体来说,`std::deque` 的底层实现由一个指向元素块的指针数组和每个元素块的双向链表组成。每个元素块包含一个固定数量的元素,而指针数组存储每个元素块的指针。在 `std::deque` 中,元素块的大小通常是 512 字节左右(具体大小可能因编译器和实现而异),并且每个元素块中的元素数量通常是 16、32 或 64 个。

由于 `std::deque` 的底层实现使用了双向链表的数组,因此在 `std::deque` 中插入和删除操作的时间复杂度是 O(1) 的,而不受元素数量的影响。同时,由于 `std::deque` 的元素块大小是固定的,因此它可以在内存中保持高效的缓存使用,从而提高程序的性能。

总之,`std::deque` 的底层实现使用了一种类似于“分块”的数据结构,它兼具双向链表和动态数组的优点,在元素插入和删除方面具有很好的性能表现,是一种高效的序列容器。

四、priority_queue的底层实现原理?

`std::priority_queue` 的底层实现原理是通过堆来实现的,常见的实现方式是使用二叉堆。

二叉堆是一种完全二叉树,它分为最大堆和最小堆两种类型。在最大堆中,父节点的值大于等于它的子节点的值,最大值在堆顶;在最小堆中,父节点的值小于等于它的子节点的值,最小值在堆顶。在 C++ STL 中,`std::priority_queue` 默认使用最大堆。

插入元素时,新的元素被插入到堆的末尾,然后与它的父节点比较。如果新元素比父节点大(或小),则它和父节点交换位置,直到满足堆的性质。在弹出元素时,堆顶的元素被弹出,然后将堆的最后一个元素移动到堆顶,再根据堆的性质进行调整,使其满足堆的性质。

由于堆的性质,每次插入和弹出元素的时间复杂度为 $O(\log n)$,其中 $n$ 是堆中元素的个数。因此,`std::priority_queue` 可以在 $O(n \log n)$ 的时间内完成排序操作。

五、multiset的底层实现原理?

`std::multiset` 是 C++ STL 中的关联式容器,它可以存储任意类型的元素,并按照元素的大小进行排序。它的底层实现是红黑树。

红黑树是一种自平衡二叉搜索树,它通过保持一些性质来保证树的平衡。在红黑树中,每个节点都有一个颜色,可以是红色或黑色。在插入或删除节点时,通过变换节点的颜色和旋转子树来保持树的平衡。

在 `std::multiset` 中,每个元素都存储在一个节点中,节点中包含元素的值、指向父节点、左子节点和右子节点的指针,以及节点的颜色。当插入元素时,新元素被插入到红黑树中,然后根据元素的大小和树的性质进行调整,以保证树的平衡。在删除元素时,被删除的元素所在的节点被删除,并根据树的性质进行调整,以保证树的平衡。

由于红黑树的性质,每次插入、删除和查找元素的时间复杂度为 $O(\log n)$,其中 $n$ 是 `std::multiset` 中元素的个数。因此,`std::multiset` 可以在 $O(n \log n)$ 的时间内完成排序和查找操作。

六、unordered_map的底层实现原理,哈希表的底层实现原理?

unordered_map 底层使用哈希表(Hash Table)实现,哈希表是一种以键值对形式存储数据的数据结构,它通过将键映射到一个哈希表中的索引来实现快速的查找。

在 unordered_map 中,哈希表是一个由桶(bucket)组成的数组,每个桶中存储着一个链表或者红黑树,用于解决哈希冲突。为了快速确定键值对应的桶,需要先对键进行哈希计算,得到一个哈希值,然后将哈希值映射到桶的索引上,最终在对应的桶中查找键对应的值

哈希表的优点在于可以实现常数时间复杂度的插入、删除和查找操作,这些操作的平均时间复杂度为 $O(1)$,在最坏情况下的时间复杂度为 $O(n)$。然而,哈希表需要耗费大量的内存来存储桶和链表或红黑树,而且其性能会受到哈希冲突的影响。

在实际使用 unordered_map 时,我们需要注意哈希函数的选择,好的哈希函数能够最大限度地避免哈希冲突,提高哈希表的性能。同时,当元素数量变化较大时,需要及时对哈希表进行扩容或缩容,以保持其性能和空间的平衡。

什么时候使用vector、list以及deque?

在使用 STL 容器时,需要根据实际情况选择适合的容器,以达到最优的性能和易用性。

- 当需要在容器的尾部进行快速的插入和删除操作,并且不需要在容器中间进行大量的元素插入和删除操作时,可以使用 `std::vector`。`std::vector` 内部使用动态数组实现,可以在 O(1) 时间内对尾部进行元素的插入和删除操作。此外,由于 `std::vector` 在内存中使用连续的内存块存储元素,因此它在访问元素时具有较好的缓存性能,适合用于存储大量连续的元素

- 当需要在容器的任意位置进行快速的插入和删除操作,并且元素数量不太多时,可以使用 `std::list`。`std::list` 内部使用双向链表实现,可以在 O(1) 时间内对任意位置的元素进行插入和删除操作,但是由于 `std::list` 在内存中存储的元素是分散的,因此访问元素时具有较差的缓存性能。`std::list` 适合用于存储数量较少的元素,但需要频繁进行元素插入和删除操作的场景。

- 当需要在容器的任意位置进行插入和删除操作,并且需要高效地在容器的头尾进行元素的插入和删除操作时,可以使用 `std::deque`。`std::deque` 内部使用一个双向链表的数组实现,可以在 O(1) 时间内对容器的头尾进行元素的插入和删除操作,也可以在 O(1) 时间内对任意位置的元素进行插入和删除操作。`std::deque` 在内存中的布局与 `std::vector` 类似,但是由于元素块的大小是固定的,因此 `std::deque` 的缓存性能优于 `std::list`。

因此,根据实际情况选择适合的容器可以帮助我们提高程序的性能和易用性。需要注意的是,在 STL 容器中选择不同的容器会影响程序的内存占用、插入和删除操作的效率等方面,需要综合考虑使用场景和需求。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值