C++ STL Deque 高频面试题与答案

目录

[Q1: 什么是 C++ STL 中的 deque?](#Q1: 什么是 C++ STL 中的 deque?)

[Q2: deque 的内部数据结构是怎样的?](#Q2: deque 的内部数据结构是怎样的?)

[Q3: deque 主要操作的时间复杂度是多少?](#Q3: deque 主要操作的时间复杂度是多少?)

[Q4: deque 和 vector 有什么主要区别?](#Q4: deque 和 vector 有什么主要区别?)

[Q5: deque 和 list 有什么主要区别?](#Q5: deque 和 list 有什么主要区别?)

[Q6: deque 的迭代器失效规则是怎样的?](#Q6: deque 的迭代器失效规则是怎样的?)

[Q7: 什么时候应该使用 deque?](#Q7: 什么时候应该使用 deque?)


deque(double-ended queue,双端队列)是 C++ STL 中一个非常有用的序列容器。它允许在序列的两端(前端和后端)进行快速的插入和删除操作。

Q1: 什么是 C++ STL 中的 deque

A1: deque 是一个序列容器,它支持在容器的两端(前端和后端)进行高效的插入和删除操作。它也支持通过索引([].at())进行快速的随机访问。

它的行为就像一个 vectorlist 的混合体,提供了类似 vector 的随机访问能力,又具备了类似 list 的两端高效插入/删除能力。

Q2: deque 的内部数据结构是怎样的?

A2: deque 的内部实现通常比 vector 复杂。它不是一块连续的内存。

相反,deque 内部通常由一个"中控器 "(或称为"map ",但这里的 map 不是 std::map)来管理。这个中控器是一个指针数组(或指针的指针),它指向多个固定大小的内存块(chunks/blocks)。

  • 中控器(Map): 是一个连续的内存块,存储指向各个数据块的指针。

  • 数据块(Chunks): 是实际存储元素的连续内存块。

当在 deque 的前端或后端插入元素时:

  1. 如果对应方向的数据块未满,元素被直接放入该块。

  2. 如果该数据块已满,deque 会分配一个新的数据块,并将新元素放入,同时在中控器(map)中添加指向这个新数据块的指针。

  3. 如果中控器(map)本身也满了,deque 会分配一个新的、更大的中控器,将旧中控器的内容拷贝过去,然后释放旧中控器。

这种分块的结构使得它可以在两端高效地增长,而不需要像 vector 那样移动所有元素。

Q3: deque 主要操作的时间复杂度是多少?

A3:

操作 时间复杂度 备注
随机访问 ([], .at()) O(1) 几乎是 O(1)。严格来说,它需要一次中控器索引和一次数据块内偏移,但都是常数时间。
前端插入/删除 (push_front, pop_front) O(1) (均摊) 均摊复杂度。在大多数情况下是 O(1),但如果需要分配新数据块或扩展中控器,则会产生额外开销。
后端插入/删除 (push_back, pop_back) O(1) (均摊) 同上。
中间插入/删除 O(N) Ndeque 中元素的数量。这涉及移动插入/删除点之后(或之前)的所有元素,效率很低。

Q4: dequevector 有什么主要区别?

A4: 这是最常被问到的问题之一。

特性 vector (动态数组) deque (双端队列)
内存布局 连续内存 (所有元素在一整块内存中) 分块的连续内存 (通过中控器管理多个数据块)
前端插入/删除 O(N) (效率极低,需移动所有元素) O(1) (均摊,非常高效)
后端插入/删除 O(1) (均摊) O(1) (均摊)
中间插入/删除 O(N) O(N) (通常比 vector 慢,因为可能涉及跨数据块的移动)
随机访问 O(1) (最快,一次指针偏移) O(1) (也很快,但比 vector 略慢,涉及两次指针操作)
内存重新分配 当容量不足时,重新分配一块更大的 内存,并移动所有元素。 在两端添加时,只需分配新的数据块,无需移动已有元素。仅当中控器满时才需重新分配中控器。
迭代器 连续内存,是普通的指针。 非普通指针,是一个特殊的类,内部维护指向中控器、数据块和当前元素的指针。

总结:

  • 如果你需要频繁在前端 插入/删除,请使用 deque

  • 如果你只需要在后端 操作,并且需要最快 的随机访问和绝对连续 的内存(例如与 C API 交互),请使用 vector

  • 如果你在乎内存占用的连续性,vector 更好;deque 会产生内存碎片(多个小块)。

Q5: dequelist 有什么主要区别?

A5:

特性 list (双向链表) deque (双端队列)
内存布局 非连续内存 (每个节点单独分配) 分块的连续内存
随机访问 O(N) (不支持 [],只能遍历) O(1) (支持 [],非常高效)
前端插入/删除 O(1) (常数时间) O(1) (均摊常数时间)
后端插入/删除 O(1) (常数时间) O(1) (均摊常数时间)
中间插入/删除 O(1) (如果已有迭代器指向该位置) O(N) (效率很低)
迭代器失效 非常稳定。插入操作不会使迭代器失效;删除操作仅使指向被删除元素的迭代器失效。 不稳定。见 Q6。
内存开销 。每个元素都有额外的指针开销 (前驱和后继)。 中等 。有中控器和数据块管理的开销,但分摊到每个元素上通常比 list 少。

总结:

  • 如果你需要高效的随机访问 ,请在 dequevector 中选择。

  • 如果你需要频繁在中间 插入/删除元素,并且希望操作后迭代器保持有效 ,请使用 list

  • 如果你的需求集中在两端 操作,并且也需要 随机访问,deque 是完美的选择。

Q6: deque 的迭代器失效规则是怎样的?

A6: deque 的迭代器失效规则比较复杂,介于 vectorlist 之间:

  1. 在两端插入 (push_front, push_back):

    • 迭代器所有 已存在的迭代器都失效。(因为中控器(map)可能会被重新分配和拷贝,导致迭代器内部指向中控器的指针失效)。

    • 引用和指针不会失效。(因为数据块本身没有被移动)。

  2. 在两端删除 (pop_front, pop_back):

    • 迭代器 :仅指向被删除元素的迭代器失效。

    • 引用和指针 :仅指向被删除元素的引用和指针失效。

  3. 在中间插入:

    • 所有 迭代器、引用和指针都失效
  4. 在中间删除:

    • 所有 迭代器、引用和指针都失效

注意: deque 在两端插入时迭代器会失效,这是它与 vector(后端插入时,若未扩容则迭代器不失效)的一个重要区别,也是面试中容易出错的地方。

Q7: 什么时候应该使用 deque

A7: 当你的应用场景满足以下一个或多个条件时,应优先考虑 deque

  1. 需要频繁在容器的前端和后端进行插入和删除操作。(例如:实现一个队列或双端队列)。

  2. 需要支持高效的随机访问([] 操作)。 (这是它优于 list 的地方)。

  3. 不希望在容器增长时(尤其是前端增长时)移动所有元素。 (这是它优于 vector 的地方)。

  4. 可以接受非连续的内存布局。

经典用例:

  • 实现一个队列(Queue)std::queue 默认就是使用 deque 作为其底层容器的。

  • 实现一个滑动窗口(Sliding Window)算法:例如"求窗口中的最大值",需要在窗口滑动时(一端出、一端进)高效操作,同时可能需要访问窗口内的元素。

  • 任务调度队列。

相关推荐
清羽_ls6 小时前
bash 基础编程的核心语法
开发语言·bash
和编程干到底6 小时前
C++基础
开发语言·c++
Z_Xshan6 小时前
docker 容器web站点 中文文件名访问404问题
linux·开发语言·docker
kkkkk0211067 小时前
【Rust创作】Rust 错误处理:从 panic 到优雅控制
开发语言·算法·rust
John.Lewis7 小时前
C++初阶(14)list
开发语言·c++·笔记
hsjkdhs8 小时前
C++文件操作
开发语言·c++
hoiii1878 小时前
C#实现近7天天气预报
开发语言·c#
赵谨言8 小时前
基于Python楼王争霸劳动竞赛数据处理分析
大数据·开发语言·经验分享·python
沐怡旸8 小时前
【穿越Effective C++】条款4:确定对象使用前已先被初始化——C++资源管理的基石
c++·面试