队列<queue>和双端队列<deque>

        队列是一种遵循先进先出(First-In-First-Out,FIFO)原则的线性数据结构。可以把它想象成现实生活中人们排队等待服务的场景,最先进入队列的人会最先得到服务并离开队列,而后来的人则需要在队列尾部依次等待。

常见操作:

  • 入队(push):也称为插入操作,将一个新元素添加到队列的尾部(队尾进队)。
  • 出队(pop):也称为删除操作,移除并返回队列头部的元素(队首出队)。
  • 判空(empty):检查队列是否为空,如果队列为空则返回 true,否则返回 false
  • 获取队头元素(front):返回队列头部的元素,但不移除它(获取队首)。
  • 获取队尾元素(back):返回队列尾部的元素,但不移除它(获取队尾)。
  • 获取队列大小(size):返回队列中当前元素的数量。
#include <iostream>
#include <queue>
using namespace std;
int main() {
    queue<int> myQueue;// 1. 定义一个存储 int 类型元素的队列
    myQueue.push(10);myQueue.push(20);
    myQueue.push(30);// 2. 入队操作(push):将元素添加到队列尾部
    cout << "当前队列的大小是: " << myQueue.size() <<endl;// 3. 获取队列大小(size):返回队列中元素的数量
    // 4. 判断队列是否为空(empty):如果队列为空返回 true,否则返回 false
    if (myQueue.empty()) {cout << "队列是空的。" << endl;}
    else {std::cout << "队列不是空的。" << std::endl;}
    // 5. 获取队头元素(front):返回队列头部的元素,但不移除它
    cout << "队头元素是: " << myQueue.front() << endl;
    // 6. 获取队尾元素(back):返回队列尾部的元素,但不移除它
    cout << "队尾元素是: " << myQueue.back() << endl;
    // 7. 出队操作(pop):移除队列头部的元素
    myQueue.pop();
    cout << "执行一次出队操作后,队头元素变为: " << myQueue.front() << endl;
    return 0;
}

<deque>(double-ended queue,双端队列)是一种双向开口的线性数据结构,兼顾了「动态数组的随机访问」和「链表的双端高效操作」,是 STL 中功能最灵活的容器之一。它也是 std::queue 和 std::stack 的默认底层适配器(替代 vector 解决双端操作效率问题)。

deque 常用操作

需包含头文件 <deque>,支持大部分与 vector 兼容的接口,同时新增双端操作专属 API:

功能函数名时间复杂度说明
双端插入push_front(x)O(1)队首插入元素 x
push_back(x)O(1)队尾插入元素 x(同 vector)
双端删除pop_front()O(1)队首删除元素(无返回值)
pop_back()O(1)队尾删除元素(同 vector)
随机访问operator[](index)O(1)访问索引 index 元素(无越界检查)
at(index)O(1)访问索引 index 元素(有越界抛出异常)
元素访问front()O(1)获取队首元素(不删除)
back()O(1)获取队尾元素(不删除)
容量操作size()O(1)获取当前元素个数
empty()O(1)判断是否为空
resize(n, val)O(n)调整容量为 n,不足补 val
clear()O(n)清空所有元素(容量可能保留)
中间操作insert(pos, x)O(n)在迭代器 pos 处插入 x
erase(pos)O(n)删除迭代器 pos 处元素

    deque 底层实现

    deque 之所以能兼顾「双端 O (1) 操作」和「随机访问 O (1)」,核心是 「分段连续存储 + 中控数组」 结构:

    1. 分段连续存储:数据不存储在单一连续数组中,而是分成多个固定大小的「数据块」(比如每个块存 4096 字节);
    2. 中控数组(map):一个指针数组,每个元素指向一个数据块。通过中控数组可以快速定位数据块,再通过块内偏移定位元素;
    3. 扩容逻辑
      • 队尾满时:新增一个数据块,中控数组尾部添加该块指针;
      • 队首满时:新增一个数据块,中控数组头部添加该块指针;
      • 中控数组满时:扩容中控数组(通常翻倍),但仅需拷贝指针(无需移动数据块,效率远高于 vector)。

    示意图(简化):

    中控数组(指针数组):[块1指针, 块2指针, 块3指针, ...]
    块1:[a0, a1, a2, ..., a(k-1)]  (连续)
    块2:[a(k), a(k+1), ..., a(2k-1)](连续)
    块3:[a(2k), ...]                (连续)
    

    为什么随机访问 O (1)?

    要访问索引 i 的元素:

    1. 计算 i 所在的数据块索引:block_idx = i / 块大小
    2. 计算块内偏移:offset = i % 块大小
    3. 通过中控数组的 block_idx 找到数据块,再通过 offset 访问元素 —— 两步均为 O (1)。

    posted @ 2025-02-03 09:00  mc12356  阅读(23)  评论(0)    收藏  举报  来源