首页 > 其他分享 >3.3.队列

3.3.队列

时间:2025-01-15 22:31:31浏览次数:3  
标签:std 队列 元素 vector 3.3 front rear

队列

队列是一种先进先出表,也就是说它的插入在一端进行,而删除则在另一端进行。

队列模型

队列的基本操作有入队(enqueue)出队(dequeue)。入队就是在队尾(rear)插入一个元素,出队就是在队头(front)删除并返回一个元素。

队列的实现

根栈一样,队列可以使用任何实现表的方法来实现,包括链表数组vector。下面我们来依次看一下分别如何实现。

链表实现队列
#include <iostream>

// 定义链表节点结构体
struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};

class QueueUsingList {
private:
    ListNode* front;
    ListNode* rear;
public:
    QueueUsingList() : front(nullptr), rear(nullptr) {}

    // 入队操作
    void enqueue(int value) {
        ListNode* newNode = new ListNode(value);
        if (rear == nullptr) {
            front = rear = newNode;
        } else {
            rear->next = newNode;
            rear = newNode;
        }
    }

    // 出队操作
    int dequeue() {
        if (front == nullptr) {
            std::cerr << "Queue is empty!" << std::endl;
            return -1;
        }
        int value = front->val;
        ListNode* temp = front;
        front = front->next;
        if (front == nullptr) {
            rear = nullptr;
        }
        delete temp;
        return value;
    }

    // 查看队首元素
    int peek() {
        if (front == nullptr) {
            std::cerr << "Queue is empty!" << std::endl;
            return -1;
        }
        return front->val;
    }

    // 检查队列是否为空
    bool isEmpty() {
        return front == nullptr;
    }
};

首先定义了一个链表中的节点的结构体,我们在前面其实了解过,节点包括一个数值和一个next指针

接着定义了一个QueueUsingList 类实现了基于链表的队列。其中:

  • frontrear 分别指向队列的队首和队尾。
  • enqueue 方法创建新节点,将其添加到队尾。若队列为空,同时更新 frontrear;若不为空,只更新 rear
  • dequeue 方法移除队首元素,更新 front,并释放原队首节点的内存。若队列为空,输出错误信息。
  • peek 方法返回队首元素的值,若队列为空,输出错误信息。
  • isEmpty 方法通过检查 front 是否为 nullptr 来判断队列是否为空。
数组实现队列
#include <iostream>
#include <array>
#include <cassert>

const int MAX_SIZE = 100;  // 假设队列最大容量为 100

class QueueUsingArray {
private:
    std::array<int, MAX_SIZE> arr;
    int front;
    int rear;
    int size;
public:
    QueueUsingArray() : front(0), rear(0), size(0) {}

    // 入队操作
    void enqueue(int value) {
        assert(size < MAX_SIZE);  // 确保队列未满
        arr[rear] = value;
        rear = (rear + 1) % MAX_SIZE;
        size++;
    }

    // 出队操作
    int dequeue() {
        if (size == 0) {
            std::cerr << "Queue is empty!" << std::endl;
            return -1;
        }
        int value = arr[front];
        front = (front + 1) % MAX_SIZE;
        size--;
        return value;
    }

    // 查看队首元素
    int peek() {
        if (size == 0) {
            std::cerr << "Queue is empty!" << std::endl;
            return -1;
        }
        return arr[front];
    }

    // 检查队列是否为空
    bool isEmpty() {
        return size == 0;
    }
};

这里指定了队列的最大容量为100,实际写代码过程中可以根据实际情况进行调整。

这里直接定义了一个QueueUsingArray 类,使用 std::array 存储队列元素,front 指向队首元素,rear 指向队尾元素的下一个位置。其中:

  • enqueue 方法将元素添加到 rear 位置,并更新 rear 指针,使用取模运算实现循环队列。
  • dequeue 方法移除队首元素,更新 front 指针,使用取模运算实现循环队列。
  • peek 方法返回队首元素,若队列为空,输出错误信息。
  • isEmpty 方法通过检查 size 是否为 0 来判断队列是否为空。

在数组实现中,我们使用了**循环数组(circular array)**实现,因为一个数组的空间是有限的,不管用不用,c++已经把这部分内存分给了数组,那么试想如果我们不断的入队出队,数组的前端是不是会出现大量的空位置,而可利用的空间越来越少,直至最后一个,而如果让rear指针再往后移就会越界,所以我们采用循环数组,让rear指针指向最后一个的下一个位置时返回数组的开头,也就是第一个位置,这样就避免越界和可以重复使用数组空间。

如下图模拟了一个循环数组实现队列的过程:

数组实现队列

数组实现队列

vector实现数组
#include <iostream>
#include <vector>

class QueueUsingVector {
private:
    std::vector<int> v;
public:
    // 入队操作
    void enqueue(int value) {
        v.push_back(value);
    }

    // 出队操作
    int dequeue() {
        if (v.empty()) {
            std::cerr << "Queue is empty!" << std::endl;
            return -1;
        }
        int value = v.front();
        v.erase(v.begin());
        return value;
    }

    // 查看队首元素
    int peek() {
        if (v.empty()) {
            std::cerr << "Queue is empty!" << std::endl;
            return -1;
        }
        return v.front();
    }

    // 检查队列是否为空
    bool isEmpty() {
        return v.empty();
    }
};

QueueUsingVector 类使用 std::vector 存储队列元素,其中:

  • enqueue 方法使用 push_backvector 尾部添加元素。
  • dequeue 方法使用 erase 移除 vector 的第一个元素,获取其值。若队列为空,输出错误信息。
  • peek 方法使用 front 方法获取队首元素,若队列为空,输出错误信息。
  • isEmpty 方法通过 v.empty() 判断队列是否为空。
总结

上述的三种方法实现队列中,使用数组实现的队列为循环队列,最大容量为 MAX_SIZE,使用链表和 vector 实现的队列则可以动态扩容。无论哪种方法实现队列,复杂度都是O(1),非常高效。

那么同栈一样,c++的STL库中一样提供了queue库来供我们直接使用,下面一起来看看吧

STL库中的队列(queue)
#include <iostream>
#include <queue>

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

    // 入队操作
    q.push(1);
    q.push(2);
    q.push(3);

    // 查看队首元素
    std::cout << "Front element: " << q.front() << std::endl;

    // 出队操作
    q.pop();
    std::cout << "Front element after pop: " << q.front() << std::endl;

    // 队列大小
    std::cout << "Queue size: " << q.size() << std::endl;

    // 检查队列是否为空
    if (q.empty()) {
        std::cout << "Queue is empty." << std::endl;
    } else {
        std::cout << "Queue is not empty." << std::endl;
    }

    return 0;
}

相较于上面三种我们自己实现,直接使用封装好的queue库显然更简单。

  • q.push(1);:将元素 1 入队。
  • q.front();:返回队首元素,但不将其从队列中移除。
  • q.pop();:将队首元素从队列中移除。
  • q.size();:返回队列中元素的数量。
  • q.empty();:检查队列是否为空,如果队列为空,返回 true,否则返回 false
拓展

std::queue 是一个容器适配器,它默认使用 std::deque 作为底层容器,但也可以使用其他容器,如 std::liststd::vector。比如使用 std::list 作为底层容器:

std::queue<int, std::list> q;

只用更改定义队列这一行即可,其它使用接口和方法默认一样。但是我们不建议更改底层容器,因为 std::deque 在性能上对于**队列的操作(入队和出队)**具有较好的平衡,特别是对于频繁在两端插入和删除元素的情况。std::queue默认使用std::deque作为底层容器,这是因为std::deque具有以下优点:

  • 可以高效地在两端进行插入和删除操作,时间复杂度为O(1)
  • 提供了动态的空间分配,不会像 std::vector 那样在内存增长时需要大量的复制操作。
  • 不像 std::list 那样每个元素需要额外的指针存储,相对更节省内存空间。

选择不同底层容器的考虑因素:

  • std::list 作为底层容器:
    • 当需要频繁的插入和删除元素,尤其是在队列中间进行插入和删除操作时,使用 std::list 作为底层容器会更合适。
    • std::list 对元素的插入和删除操作具有 的时间复杂度,不会像 std::vector 那样引起元素的大量移动。
    • 然而,std::list 会使用额外的内存来存储节点之间的指针,每个元素的存储开销相对较大。
  • std::vector 作为底层容器:
    • 如果对内存使用较为敏感,且入队操作不会频繁导致 std::vector 扩容,使用 std::vector 作为底层容器可以节省一些额外的指针空间。
    • 但是,std::vector 在扩容时会涉及到元素的复制或移动,这可能会导致性能下降。如果入队操作频繁导致 std::vector 不断扩容,性能会受到一定影响。
  • std::deque 作为底层容器(默认):
    • 通常情况下,std::deque 是一个不错的折衷选择,它在两端的操作性能都比较好,既避免了 std::vector 扩容时的复制开销,又不像 std::list 那样需要额外的指针存储。

总结:

  • std::queue 的底层容器选择取决于具体的应用场景和性能需求。
  • 对于大多数情况,使用默认的 std::deque 可以满足需求,它在性能和内存使用上取得了较好的平衡。
  • 若需要频繁的元素插入和删除操作,可以考虑使用 std::list
  • 若对内存使用比较敏感且对性能有一定的要求,可以根据实际情况考虑使用 std::vector,但需要注意扩容带来的性能开销。

无论使用哪种底层容器,std::queue 提供的接口都是相同的,这保证了代码的一致性和可移植性,方便开发者根据不同的需求灵活选择底层容器。

标签:std,队列,元素,vector,3.3,front,rear
From: https://blog.csdn.net/m0_60046831/article/details/145137036

相关文章

  • 初阶数据结构【队列及其接口的实现】
    目录前言一、队列的概念及结构二、队列的实现方式三、队列的实现3.1基本结构3.2队列基本功能接口初始化队列销毁队列3.3入队列接口3.4出队列接口3.5队列的其它接口获取队列头部元素获取队列队尾元素检测队列是否为空获取队列中有效元素个数3.6测试总结前言......
  • linux编译protobuf-3.3.0 报错 automake-1.14 command not found 解决
    目录源码下载配置编译解决REFlinux编译protobuf-3.3.0报错automake-1.14:commandnotfound解决源码下载https://github.com/protocolbuffers/protobuf/releases配置编译配置完成后,编译出错./configuremakecd.&&/bin/bash/tmp/protobuf-3.3.0/miss......
  • 缓存提速+队列削峰+分库分表+读写分离
    项目背景由于访问量超出设计预期,12306网站在高峰期出现了一系列问题:页面打开缓慢查询和下单报错后台系统过载用户体验不佳根因分析请求高峰(类似于秒杀)响应迟缓  放票时高并发的下单集中在一起,形成请求高峰(类似于秒杀),请求导致订单/电子客票数据......
  • RabbitMQ-死信队列
    死信,就是无法被消费的消息,一般来说生产者将消息投递到broker或者直接到队列里了,消费者从队列取出消息进行消费。但某些时候由于特定的原因导致队列中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有死信队列。死信队列还是队列---只是用来接受特......
  • RabbitMQ-优先级队列及消息配置
    优先级队列C#数据类型queue----先进先出RabbitMQ---队列-----默认也是先进先出~~RabbitMQ设置优先级----可以配置让消费顺序,不按照先进先出的默认规则;给定的优先级---最终体现在消费者;优先级越高,消费的时候,就优先消费。就在前面消费案例:设置{"vip1","hello2","wor......
  • Java-数据结构-栈与队列(常考面试题与单调栈)
    在上一篇的学习中,我们学习了栈和队列的基本知识,以及它们对应都有哪些方法,在什么应用场景下如何使用,并且还对它们进行了模拟实现,而其实对于栈和队列的相关知识还远不止于此,而今天我们就对栈与队列进行复盘,认识更多使用它们的场景,夯实代码功底吧~一、常考面试题-思维以下习题在......
  • Java算法 数据结构 栈 队列 优先队列 比较器
    目录栈Stack性质构造方法代码示例队列Queue性质构造方法代码示例优先队列PriorityQueue性质构造方法代码示例比较器1.Comparator接口的方法2.常见的内置比较器1.自然排序比较器(naturalOrder())2.逆序排序比较器(reverseOrder())3.nullsFirst()......
  • leetcode周赛432 T4(单调栈 + 单调队列)
    一道练习单调栈+单调队列的好题题目链接:problem对于求合法子数组数量的题目,可以先考虑传统的枚举右端点,二分左端点的套路。此题用这种方法恰好可行,因为对于一个序列,左端增加一个数不会让操作数更少。因此对于固定右端点,合法的左端点一定是一段区间。所以现在问题转化为:用双指......
  • 数据结构:栈(Stack)和队列(Queue)—面试题(二)
    1.用队列实现栈。习题链接https://leetcode.cn/problems/implement-stack-using-queues/description/描述:请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。实现 MyStack 类:voidpush(intx) 将元素x压入栈顶。int......
  • 验证出栈顺序是否正确题解&队列
    (1)验证出栈顺序是否正确队列遵循先入后出的原则,故需要一个数组来模拟记录入栈和出栈再分别对出栈顺序进行遍历验证是否正确#include<iostream>usingnamespacestd;#definem100000intb[m],a[m],c[m];intmain(){ intt; cin>>t; while(t--){ intn;......