STL——栈和队列和优先队列

概述

学到这里对于容器的基础内容都是大概掌握了的,所以直接讲重点

栈(Stack)

栈是一种后进先出(LIFO, Last In First Out)的数据结构,类比为一个容器,你只能从容器的顶部(栈顶)添加或移除元素,不能在中间或底部进行操作。栈的基本操作包括:

  • 压入(Push):将元素放入栈顶。
  • 弹出(Pop):从栈顶移除元素。
  • 查看栈顶元素(Top):查看但不移除栈顶元素。
  • 判空(Empty):判断栈是否为空。
  • 大小(Size):获取栈中元素的个数。

栈常用于需要“后进先出”操作的场景,比如函数调用的执行过程(存储局部变量和调用信息)、表达式求值(处理运算符优先级)、浏览器的返回按钮(记录访问历史)等。

队列(Queue)

队列是一种先进先出(FIFO, First In First Out)的数据结构,类比为排队,你只能从队列的一端(队尾)添加元素,从另一端(队首)移除元素。队列的基本操作包括:

  • 入队(Enqueue):将元素添加到队列的末尾。
  • 出队(Dequeue):从队列的头部移除元素。
  • 查看队首元素(Front):查看但不移除队首元素。
  • 查看队尾元素(Back):查看但不移除队尾元素。
  • 判空(Empty):判断队列是否为空。
  • 大小(Size):获取队列中元素的个数。

队列常用于需要“先进先出”操作的场景,比如任务调度(先到先服务)、缓冲管理(网络数据包传输)、打印队列(打印机任务管理)等。

优先队列(Priority Queue)

优先队列是一种根据元素的优先级来确定出队顺序的队列,而不是严格的先进先出。具体来说,每次出队操作都会返回具有最高(或最低)优先级的元素。优先队列的特点是:

元素之间有优先级顺序,高优先级的元素先出队。

通常使用堆(Heap)这种数据结构来实现,因为堆能够高效地支持插入和删除操作,并保持元素的部分有序性。

基本操作包括插入元素、获取顶部元素(具有最高优先级的元素)、删除顶部元素等。

优先队列常用于需要动态维护一组数据中的优先级顺序的场景,比如任务调度(优先级高的任务优先执行)、事件模拟(按照发生时间的优先级处理事件)等。

在这里插入图片描述

std::堆栈

template <class T, class Container = deque > class stack;

后进先出堆栈

堆栈是一种容器适配器,专门设计用于在后进先出环境(后进先出)下运行,其中元素仅从容器的一端插入和提取。

堆栈S 作为容器适配器实现,这些适配器是使用特定容器类的封装对象作为其底层容器的类,提供一组特定的成员函数来访问其元素。元素从特定容器的“背面”推出/弹出,该容器称为堆栈的顶部。

底层容器可以是任何标准容器类模板,也可以是其他一些专门设计的容器类。容器应支持以下操作:

empty
size
back
push_back
pop_back

标准容器类,并满足这些要求。默认情况下,如果未为特定类实例化指定容器类,则使用标准容器。

核心函数和操作

构造函数和析构函数

  • stack:创建一个空栈,其中 T 是存储的数据类型。
  • stack(const stack& other):复制构造函数,用于复制另一个栈的内容。

成员函数

  • push(const T& value):将元素压入栈顶。
  • pop():从栈顶移除元素,不返回任何值。
  • top():返回栈顶元素的引用,但不将其从栈中移除。
  • empty():检查栈是否为空,返回 true 或 false。
  • size():返回栈中元素的数量。

示例

#include <iostream>
#include <stack>

int main() {
    std::stack<int> s;

    // Pushing elements onto the stack
    s.push(10);
    s.push(20);
    s.push(30);

    // Checking if stack is empty
    if (!s.empty()) {
        std::cout << "Stack size: " << s.size() << std::endl;

        // Accessing top element
        std::cout << "Top element: " << s.top() << std::endl;

        // Popping elements
        s.pop();
        std::cout << "Popped top element." << std::endl;

        // Accessing top element after popping
        std::cout << "Top element now: " << s.top() << std::endl;
    }

    return 0;
}

注意事项

访问栈顶元素:使用 top() 函数之前,需要确保栈非空,否则会导致未定义行为。

移除栈顶元素:使用 pop() 函数会移除栈顶元素,但不返回该元素的值。

复制栈:使用复制构造函数和赋值操作符时,会复制整个栈的内容,包括元素顺序。

空栈访问:尝试在空栈上调用 top() 或 pop() 会导致运行时错误(未定义行为),因此在使用这些操作前应当检查栈是否为空。

std::队列

template <class T, class Container = deque > class queue;

FIFO队列

队列S 是一种容器适配器,专门设计用于在 FIFO 上下文(先进先出)中运行,其中元素插入容器的一端并从另一端提取。

队列 S 作为容器适配器实现,这些适配器是使用特定容器类的封装对象作为其底层容器的类,提供一组特定的成员函数来访问其元素。元素被推入特定容器的“后部”,并从其“前部”弹出。

底层容器可以是标准容器类模板之一,也可以是其他一些专门设计的容器类。此基础容器应至少支持以下操作:
empty
size
front
back
push_back
pop_front

标准容器类别,并满足这些要求。默认情况下,如果未为特定类实例化指定容器类,则使用标准容器。

核心函数和操作

构造函数和析构函数

  • queue:创建一个空队列,其中 T 是存储的数据类型。
  • queue(const queue& other):复制构造函数,用于复制另一个队列的内容。

成员函数

  • push(const T& value):将元素插入队列的末尾。
  • pop():从队列的开头移除元素,不返回任何值。
  • front():返回队列的第一个元素的引用,但不将其从队列中移除。
  • back():返回队列的最后一个元素的引用,但不将其从队列中移除。
  • empty():检查队列是否为空,返回 true 或 false。
  • size():返回队列中元素的数量。

示例

#include <iostream>
#include <queue>

int main() {
    std::queue<int> q;

    // Pushing elements into the queue
    q.push(10);
    q.push(20);
    q.push(30);

    // Checking if queue is empty
    if (!q.empty()) {
        std::cout << "Queue size: " << q.size() << std::endl;

        // Accessing front and back elements
        std::cout << "Front element: " << q.front() << std::endl;
        std::cout << "Back element: " << q.back() << std::endl;

        // Popping elements
        q.pop();
        std::cout << "Popped front element." << std::endl;

        // Accessing front element after popping
        std::cout << "Front element now: " << q.front() << std::endl;
    }

    return 0;
}

注意事项

访问队列元素:使用 front() 和 back() 函数之前,需要确保队列非空,否则会导致未定义行为。

移除队列元素:使用 pop() 函数会移除队列的第一个元素,但不返回该元素的值。

复制队列:使用复制构造函数和赋值操作符时,会复制整个队列的内容,包括元素顺序。

空队列访问:尝试在空队列上调用 front()、back() 或 pop() 会导致运行时错误(未定义行为),因此在使用这些操作前应当检查队列是否为空。

std::优先队列

底层实现原理

在实现优先队列时,通常使用以下两种主要数据结构:

基于堆(Heap)的实现

堆是一种特殊的二叉树,通常实现为数组。

最大堆(Max Heap):父节点的值总是大于或等于任何一个子节点的值。

最小堆(Min Heap):父节点的值总是小于或等于任何一个子节点的值。

在最大堆中,根节点是优先级最高的元素;在最小堆中,根节点是优先级最低的元素。

平衡二叉搜索树的实现

使用平衡二叉搜索树(如红黑树)来维护有序序列,以支持快速的插入、删除和查找操作。

这种实现保证了元素能够按照优先级有序地存储和访问。

效率分析

插入操作

基于堆的优先队列中,插入操作的时间复杂度为 O(log n),其中 n 是当前队列中元素的数量。这是因为插入新元素后,需要调整堆以恢复堆的性质。
平衡二叉搜索树实现的优先队列中,插入操作的时间复杂度为 O(log n),其中 n 是当前队列中元素的数量。这是因为需要保持树的平衡特性。

删除操作

删除操作是指移除优先队列中优先级最高的元素。在基于堆的实现中,删除操作的时间复杂度为 O(log n),因为移除根节点后,需要进行堆的重排。
平衡二叉搜索树实现的优先队列中,删除操作的时间复杂度为 O(log n),因为需要保持树的平衡。

获取最高优先级元素

获取优先队列中优先级最高的元素(即队头元素)的时间复杂度为 O(1)。这是因为在堆中,根节点始终是最大或最小值;在平衡二叉搜索树中,最小或最大元素也可以通过一些常数时间的操作得到。

deque双端队列

deque(双端队列)是C++标准模板库(STL)中的一种容器,它结合了向量(vector)和链表(list)的优点,支持在两端高效地进行元素的插入和删除操作。下面是关于 deque 容器的原理和效率的详细解释:

原理

deque 的名称来源于 “double-ended queue”,即双端队列。它通过一系列的块(chunks)来存储元素,每个块内部通常是一个固定大小的数组,这个数组可以容纳多个元素。deque 的关键设计包括:

块结构:

deque 内部通常由多个块组成,每个块是一个固定大小的数组(或者链表节点)。
每个块存储元素,并且每个块具有前驱和后继块的指针,形成了一个双向链表结构。

指针管理:

deque 维护了指向第一个块和最后一个块的指针,使得在队列的头部和尾部进行快速插入和删除操作成为可能。

对于头部和尾部的插入和删除操作,通过调整指针和在必要时创建或销毁块来维护块的结构。

元素访问:

deque 允许使用迭代器访问元素,迭代器支持双向遍历,并且支持随机访问(通过双向迭代器和块索引的组合实现)。
内存分配:

deque 内部使用动态内存分配来管理块的创建和销毁。这种方式避免了频繁的内存重分配,提高了插入和删除操作的效率。
效率
deque 提供了以下操作的平均时间复杂度:

头部插入和删除(push_front, pop_front):O(1)
尾部插入和删除(push_back, pop_back):O(1)
随机访问(通过迭代器或下标访问):O(1)
相比于 vector 和 list,deque 的主要优势在于

头部操作的高效性:与 vector 相比,deque 的头部插入和删除操作更为高效,因为 vector 需要移动大量元素。
尾部操作的高效性:与 list 相比,deque 在尾部插入和删除操作上的效率更高,因为 list 的每个元素都需要一个额外的指针。

在这里插入图片描述

示例代码

#include <deque>
#include <iostream>

int main() {
    std::deque<int> dq;

    dq.push_back(1);  // 尾部插入
    dq.push_front(2); // 头部插入

    std::cout << dq.front() << " " << dq.back() << std::endl; // 输出:2 1

    dq.pop_back();    // 尾部删除
    dq.pop_front();   // 头部删除

    return 0;
}
  • 19
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值