C++ list与deque的区别
在 C++ 标准模板库(STL)中,std::list 和 std::deque 都是常用的容器,它们在很多方面存在差异,以下为你详细分析:
1. 底层数据结构
- std::list:是双向链表。每个节点包含数据和两个指针,分别指向前一个节点和后一个节点。这种结构使得节点在内存中是离散存储的,不要求连续的内存空间。
- std::deque:是双端队列,它由多个固定大小的连续内存块组成,这些内存块通过一个映射数组管理。每个内存块内部是连续的,但不同内存块之间不一定连续。
2. 插入和删除操作
- 头部和尾部插入 / 删除std::list:在头部和尾部进行插入和删除操作的时间复杂度都是 \(O(1)\)。因为只需要修改相邻节点的指针即可。std::deque:同样,在头部和尾部进行插入和删除操作的时间复杂度也是 \(O(1)\)。不过 std::deque 可能需要在内存块满时分配新的内存块。
- 中间插入 / 删除std::list:在中间插入或删除元素的时间复杂度为 \(O(1)\),前提是已经有指向该位置的迭代器。因为只需要修改相邻节点的指针。std::deque:在中间插入或删除元素的时间复杂度为 \(O(n)\),因为需要移动元素以保持连续性。
3. 随机访问
- std::list:不支持随机访问。要访问第 n 个元素,需要从头部或尾部开始遍历,时间复杂度为 \(O(n)\)。
- std::deque:支持随机访问,使用 [] 或 at() 操作符可以直接访问第 n 个元素,时间复杂度为 \(O(1)\)。
4. 内存使用
- std::list:由于每个节点都需要额外的指针,因此内存开销较大。尤其是对于存储小对象的情况,指针所占的内存比例会更高。
- std::deque:除了存储数据本身,还需要额外的映射数组来管理内存块,因此也有一定的内存开销。但相比于 std::list,对于存储小对象的情况,std::deque 的内存利用率可能更高。
5. 迭代器失效情况
- std::list:插入和删除操作只会使指向被操作节点的迭代器失效,其他迭代器不受影响。
- std::deque:在头部或尾部插入元素时,迭代器通常不会失效,但在中间插入或删除元素时,所有迭代器都会失效。
6. 适用场景
- std::list:适用于需要频繁在中间插入或删除元素,且不需要随机访问的场景,例如实现一个任务队列,需要不断地在队列中间插入或删除任务。
- std::deque:适用于需要频繁在头部和尾部插入或删除元素,同时也需要随机访问的场景,例如实现一个双端队列,需要在队列两端进行操作,并且偶尔需要随机访问队列中的元素。
代码示例
cpp
#include <iostream>
#include <list>
#include <deque>
int main() {
// std::list 示例
std::list<int> myList = {1, 2, 3};
myList.push_front(0);
myList.push_back(4);
for (auto it = myList.begin(); it != myList.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// std::deque 示例
std::deque<int> myDeque = {1, 2, 3};
myDeque.push_front(0);
myDeque.push_back(4);
for (size_t i = 0; i < myDeque.size(); ++i) {
std::cout << myDeque[i] << " ";
}
std::cout << std::endl;
return 0;
}
上述代码展示了 std::list 和 std::deque 的基本使用,包括插入元素和遍历元素。