系列文章目录
文章目录
- 前言
- 一、概述
- 1.1 特点:
- 1.2 queue的工作原理和内部实现
- 二、基本操作
- 三、性能分析
- 3.1 STL队列的时间复杂度和空间复杂度
- 3.2 STL队列和自定义队列的性能差异
- 四、实例演示
- 总结
前言
-
常见的应用场景包括:
-
任务调度: 队列可用于任务调度,确保任务按照提交的顺序进行处理。先提交的任务先执行。
-
广度优先搜索(BFS): 在图算法中,BFS使用队列来管理待访问的节点,确保按照层级的顺序访问节点。
-
打印队列: 在打印系统中,打印任务被排入队列,以便按照先到先服务的原则进行打印。
-
缓冲区管理: 队列可以用于管理缓冲区,例如处理网络数据包、操作系统中的缓冲队列等。
-
消息传递: 队列常用于实现消息传递系统,其中消息按照发送的顺序进行排队和处理。
-
多线程编程: 在多线程应用程序中,队列可以用于线程之间的通信,充当线程安全的消息传递通道。
-
实现广告投放系统: 队列可以用于按照广告请求的先后顺序管理广告投放任务。
-
计算机网络中的数据传输: 在网络通信中,数据包通常以队列的形式进行传输,确保按照发送的顺序接收。
-
作业调度: 在操作系统中,队列可用于作业调度,确保作业按照提交的先后顺序执行。
-
排队系统: 在实际生活中的排队系统,如银行、超市等,顾客按照先来先服务的原则排队。
-
一、概述
C++ STL(Standard Template Library)提供了一个名为queue的容器适配器,它实现了队列的功能,即先进先出(FIFO)的数据结构。
queue容器适配器可以通过包含头文件来使用。它基于一种双端队列(deque)实现,默认情况下,queue容器使用deque作为其底层容器。
注意:
由于queue是一个适配器,它基于底层容器的操作进行实现,因此不支持直接迭代器访问。
1.1 特点:
- 元素按照插入顺序存储,但只能在队列的末尾进行插入操作。
- 只能在队列的头部进行删除操作。
- 队列没有随机访问的能力,不能直接访问和修改队列中的任意元素。
- 队列的大小可以动态增长,没有固定长度的限制。
1.2 queue的工作原理和内部实现
-
STL队列的工作原理是基于一个基础数据结构——双向链表(doubly linked list)来实现的。双向链表是由一个个节点组成的,每个节点包含一个元素和指向前一个节点和后一个节点的指针。
-
STL队列的内部实现使用了双向链表作为底层数据结构,具体来说,是以一个双向链表的尾部作为队列的头部(front),而链表的头部作为队列的尾部(back)。这样设计的目的是为了高效地进行元素的插入和删除操作。
-
当向队列中插入一个元素时,该元素将被添加到双向链表的末尾,并更新队列的尾部指针。而从队列中删除元素时,则是从链表的头部删除第一个元素,并更新队列的头部指针。
-
通过这种设计,STL队列可以快速执行插入和删除操作,而且不需要移动整个队列中的元素,因为它只需要更新头部和尾部指针即可。这种设计保证了队列的先进先出的特性,并提供了高效的性能。
此外,STL队列还提供了其他一些常用的功能,如判断队列是否为空、获取队列中元素的个数等。这些功能都是基于底层的双向链表实现的。
二、基本操作
方法 | 描述 |
---|---|
push(element) | 将元素element添加到队列的末尾。 |
emplace(element) | 将元素element添加到队列的末尾。 |
pop() | 移除队列的第一个元素。 |
front() | 返回队列的第一个元素,但不移除它。 |
back() | 返回队列的最后一个元素,但不移除它。 |
empty() | 检查队列是否为空,如果为空则返回true,否则返回false。 |
size() | 返回队列中元素的个数。 |
注意:
push是得传入得对象先得造好,再复制过去插入;而emplace则可以自己拿到构造对象所需得元素构造出来,直接插入即可。emplace相比于push省去了复制这步,即使用emplace这种操作会更节省内存。
三、性能分析
3.1 STL队列的时间复杂度和空间复杂度
STL队列的时间复杂度和空间复杂度如下:
- 入队操作(push)的时间复杂度为O(1),因为只需要在队尾添加元素。
- 出队操作(pop)的时间复杂度也为O(1),因为只需要在队首删除元素。
- 查看队首元素(front)的时间复杂度为O(1),因为只需要返回队首元素。
- 查看队尾元素(back)的时间复杂度为O(1),因为只需要返回队尾元素。
总的来说,STL队列的操作都是常数时间复杂度,即O(1)。
空间复杂度方面,STL队列的空间复杂度取决于队列中元素的个数,即为O(n),其中n为队列中的元素个数。
3.2 STL队列和自定义队列的性能差异
STL队列和自定义队列的性能差异在很大程度上取决于具体的实现方式和使用场景。以下是一些可能的差异:
- 内存管理:STL队列使用动态分配的内存来存储元素,这意味着在插入和删除元素时,可能需要频繁地重新分配和复制元素。相比之下,自定义队列可以使用静态数组或链表来存储元素,从而避免了频繁的内存分配和复制操作。
- 容量管理:STL队列具有自动扩容的能力,可以动态调整队列的大小以适应元素的变化。自定义队列可能需要手动管理容量,当队列满时,需要手动扩容。
- 性能优化:STL队列是经过优化和测试的标准容器,在大多数情况下,它具有较好的性能。自定义队列的性能则取决于具体的实现方式和算法的选择,可能需要更多的优化来达到相同的性能水平。
总的来说,STL队列在大多数情况下具有较好的性能和可靠性,尤其适用于一般的应用场景。然而,当需要对队列的性能进行特定的优化或者对内存管理有特殊要求时,自定义队列可能更加适合。
四、实例演示
示例1:
基本操作
queue<int> q;q.push(10);q.emplace(20);q.emplace(30);cout << q.empty() << endl;cout << q.size() << endl;cout << q.front() << endl;cout << q.back() << endl;q.pop();cout << q.front() << endl;
运行结果:
示例2:
常见的队列操作,如生产者-消费者模型
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>std::queue<int> q;
std::mutex mtx;
std::condition_variable cv;void producer() {for (int i = 0; i < 10; ++i) {std::this_thread::sleep_for(std::chrono::seconds(1));std::lock_guard<std::mutex> lock(mtx);q.push(i); // 生产者将数据压入队列std::cout << "Producer produced: " << i << std::endl;cv.notify_all(); // 通知消费者有数据可取}
}void consumer() {while (true) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [] { return !q.empty(); }); // 消费者等待直到队列非空int data = q.front();q.pop(); // 消费者从队列取出数据std::cout << "Consumer consumed: " << data << std::endl;lock.unlock();std::this_thread::sleep_for(std::chrono::seconds(2));}
}int main() {std::thread producerThread(producer);std::thread consumerThread(consumer);producerThread.join();consumerThread.join();return 0;
}
总结
优点和适用场景:
1. 高效性: STL队列的操作是基于deque实现的,deque是一种双端队列,插入和删除操作都是高效的。在尾部插入元素和删除元素的时间复杂度都是常数时间O(1)。
2. 先进先出(FIFO): STL队列是一种先进先出的数据结构,只能在队首移除元素,在队尾插入元素。这种特性适用于一些需要顺序处理的场景,比如任务调度、消息传递等。
3. 线程安全: STL队列在多线程环境下可以通过加锁来实现线程安全。对于需要在多个线程中同时访问的情况,可以使用STL队列来实现线程间的消息传递或任务调度。
4. 适用于BFS算法: nSTL队列适用于广度优先搜索(BFS)算法的实现。在BFS算法中,需要将遍历过的节点按照一定的顺序保存起来,以便后续处理。STL队列能够很好地满足这个需求。