#1024程序员节|征文#
C++ 中的 stack
和 queue
容器详细总结
1. 概述
C++ 标准模板库(STL)提供了一系列容器,其中 stack
和 queue
是两种常用的适配器容器。它们基于底层的序列容器(如 vector
、deque
)实现,分别用于支持栈和队列的操作模型。栈(stack
)遵循“后进先出”(LIFO)的原则,而队列(queue
)遵循“先进先出”(FIFO)的原则。本文将详细介绍这两种容器的特点、使用方法、实现机制及其应用场景。
2. stack
容器
2.1 什么是 stack
?
stack
是一种基于“后进先出”(LIFO, Last In First Out)数据结构的容器。它的特点是只能在栈的顶部进行元素的插入和删除操作。栈的操作类似于现实中的叠盘子,最新添加的盘子在最上面,也最先被移除。
2.2 stack
的特点
后进先出(LIFO):只能访问栈顶的元素,插入和删除也都只能在栈顶进行。限制性操作:stack
只允许在栈顶插入(push
)和删除(pop
)元素,无法随机访问中间的元素。适配器容器:stack
并非独立的容器,而是基于其他序列容器(如 deque
、vector
或 list
)实现的适配器。 2.3 stack
的常用操作
push(value)
:将元素压入栈顶。 #include <stack>#include <iostream>int main() { std::stack<int> s; s.push(10); // 将 10 压入栈顶 s.push(20); // 将 20 压入栈顶 return 0;}
pop()
:移除栈顶元素。 s.pop(); // 移除栈顶元素 20
top()
:访问栈顶元素。 int topElement = s.top(); // 获取栈顶元素 10
empty()
:判断栈是否为空。 if (s.empty()) { std::cout << "栈为空" << std::endl;}
size()
:返回栈中的元素个数。 std::cout << "栈的大小: " << s.size() << std::endl;
2.4 stack
的底层实现
stack
是一个容器适配器,底层通常使用 deque
作为默认的序列容器,也可以使用 vector
或 list
。由于 stack
只暴露了栈的接口,底层的容器类型对外不可见。
以下代码展示了如何使用 deque
来实现一个 stack
:
std::stack<int, std::deque<int>> s; // 使用 deque 作为底层容器
stack
的默认实现是基于 deque
,因为 deque
支持高效的头部和尾部插入与删除操作。而 vector
虽然也可以用来实现 stack
,但在需要扩展时可能需要重新分配内存,性能可能不如 deque
稳定。
2.5 stack
的应用场景
函数调用管理:在编译器的实现中,stack
常被用来管理函数调用,包括参数传递、返回地址保存等。表达式求值:在中缀表达式到后缀表达式的转换、后缀表达式的计算中,stack
都能起到重要的作用。括号匹配:stack
常用于判断括号是否匹配的问题,可以高效地检查表达式中的括号是否正确闭合。回溯问题:stack
也常用于深度优先搜索(DFS)等需要回溯的算法中。 2.6 stack
的优缺点
优点: 操作简单,只需关注栈顶的元素。插入和删除操作的时间复杂度为 O(1)。 缺点: 无法随机访问元素,限制了灵活性。只能通过 push
和 pop
操作进行数据操作。 3. queue
容器
3.1 什么是 queue
?
queue
是一种基于“先进先出”(FIFO, First In First Out)数据结构的容器。它的特点是只能在队尾插入元素,在队头删除元素,类似于排队的过程,最先进入队列的元素最先被移除。
3.2 queue
的特点
先进先出(FIFO):元素只能从队尾插入,从队头移除。适配器容器:queue
和 stack
类似,也是基于其他序列容器(如 deque
、list
)实现的适配器。限制性操作:queue
只允许在队尾插入和在队头移除,无法随机访问中间的元素。 3.3 queue
的常用操作
push(value)
:将元素添加到队尾。 #include <queue>#include <iostream>int main() { std::queue<int> q; q.push(10); // 将 10 添加到队尾 q.push(20); // 将 20 添加到队尾 return 0;}
pop()
:移除队头元素。 q.pop(); // 移除队头元素 10
front()
:访问队头元素。 int frontElement = q.front(); // 获取队头元素 20
back()
:访问队尾元素。 int backElement = q.back(); // 获取队尾元素 20
empty()
:判断队列是否为空。 if (q.empty()) { std::cout << "队列为空" << std::endl;}
size()
:返回队列中的元素个数。 std::cout << "队列的大小: " << q.size() << std::endl;
3.4 queue
的底层实现
queue
通常使用 deque
作为默认的底层容器,也可以使用 list
。deque
是双端队列,支持高效的头尾操作,因此非常适合用于实现 queue
。
以下代码展示了如何使用 list
作为底层容器实现一个 queue
:
std::queue<int, std::list<int>> q; // 使用 list 作为底层容器
3.5 queue
的应用场景
任务调度:queue
常用于任务调度中,例如打印任务、进程任务等,确保任务按照先后顺序执行。广度优先搜索(BFS):在图算法中,queue
常用于实现广度优先搜索,逐层访问节点。缓冲区:queue
常用于实现缓冲区,保证数据的顺序处理,例如在多线程编程中用于生产者-消费者模型。 3.6 queue
的优缺点
优点: 结构简单,保证元素按顺序被处理。插入和删除操作的时间复杂度为 O(1)。 缺点: 无法随机访问元素。只支持从队尾插入和从队头移除,限制了灵活性。 4. priority_queue
容器
4.1 什么是 priority_queue
?
priority_queue
是一种特殊的队列,其元素根据优先级进行排序。默认情况下,priority_queue
中的元素是按大顶堆(最大元素优先)进行排序的,即优先级最高的元素最先出队。
4.2 priority_queue
的特点
优先级排序:元素按优先级进行排序,最大或最小的元素优先出队。基于堆实现:priority_queue
通常使用堆结构(如二叉堆)来实现,以保证元素的插入和删除操作具有对数级的时间复杂度。只允许访问队头元素:与普通 queue
类似,priority_queue
只允许访问和移除队头元素,不能随机访问其他元素。 4.3 priority_queue
的常用操作
push(value)
:将元素添加到队列中,并根据优先级调整位置。 #include <queue>#include <iostream>int main() { std::priority_queue<int> pq; pq.push(30); // 将 30 添加到队列中 pq.push(10); // 将 10 添加到队列中 pq.push(20); // 将 20 添加到队列中 return 0;}
pop()
:移除优先级最高的元素。 pq.pop(); // 移除队头元素 30(最大值)
top()
:访问优先级最高的元素。 int topElement = pq.top(); // 获取优先级最高的元素 20
empty()
:判断队列是否为空。 if (pq.empty()) { std::cout << "优先队列为空" << std::endl;}
size()
:返回队列中的元素个数。 std::cout << "优先队列的大小: " << pq.size() << std::endl;
4.4 priority_queue
的底层实现
priority_queue
使用堆来实现,默认情况下是大顶堆(最大值优先),可以通过指定比较器来实现小顶堆。
以下代码展示了如何使用小顶堆来实现一个 priority_queue
:
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap; // 小顶堆template<class T>class less{ public: bool operator()(const T& x, const T& y) { return x < y; } }; //仿函数,判断大小 template<class T>class greater{public:bool operator()(const T& x, const T& y){return x > y;} }; //仿函数,判断大小————————————————
如果元素为自定义类型,则需要用户自己重载仿函数
4.5 priority_queue
的应用场景
任务调度:在操作系统中,priority_queue
可以用于实现优先级调度,让优先级高的任务先执行。最短路径算法:在图算法中,priority_queue
常用于 Dijkstra 算法,以找到权重最小的路径。事件驱动模拟:在事件驱动的模拟系统中,priority_queue
可以根据事件的优先级处理事件,确保最重要的事件最先被处理。 4.6 priority_queue
的优缺点
优点: 能够根据优先级高效地管理元素。插入和删除的时间复杂度为 O(log n),适合处理大量需要排序的元素。 缺点: 无法随机访问元素。只允许访问优先级最高的元素,限制了灵活性。 5. stack
、queue
和 priority_queue
的对比
5.1 访问方式
stack
:后进先出,只能访问栈顶元素。queue
:先进先出,只能访问队头和队尾元素。priority_queue
:按照优先级排序,只能访问优先级最高的元素。 5.2 应用场景
stack
:适用于需要后进先出逻辑的场景,例如递归、函数调用管理等。queue
:适用于需要先进先出逻辑的场景,例如任务调度、广度优先搜索等。priority_queue
:适用于需要根据优先级处理元素的场景,例如任务调度、图算法等。 5.3 底层实现
stack
:基于 deque
(默认)、vector
或 list
。queue
:基于 deque
(默认)或 list
。priority_queue
:基于堆,通常使用 vector
实现。 5.4 时间复杂度
stack
:插入和删除操作的时间复杂度为 O(1)。queue
:插入和删除操作的时间复杂度为 O(1)。priority_queue
:插入和删除操作的时间复杂度为 O(log n)。 6. 示例代码总结
以下是一个完整的示例代码,展示了 stack
、queue
和 priority_queue
的基本使用方法:
#include <iostream>#include <stack>#include <queue>int main() { // stack 示例 std::stack<int> s; s.push(1); s.push(2); s.push(3); std::cout << "Stack top: " << s.top() << std::endl; s.pop(); std::cout << "Stack top after pop: " << s.top() << std::endl; // queue 示例 std::queue<int> q; q.push(1); q.push(2); q.push(3); std::cout << "Queue front: " << q.front() << ", back: " << q.back() << std::endl; q.pop(); std::cout << "Queue front after pop: " << q.front() << std::endl; // priority_queue 示例 std::priority_queue<int> pq; pq.push(10); pq.push(5); pq.push(20); std::cout << "Priority queue top: " << pq.top() << std::endl; pq.pop(); std::cout << "Priority queue top after pop: " << pq.top() << std::endl; return 0;}
7. 总结
C++ 中的 stack
、queue
和 priority_queue
容器为开发者提供了实现特定数据结构的便捷方式。stack
采用后进先出的逻辑,适合处理递归和回溯问题;queue
采用先进先出的逻辑,适合任务调度和广度优先搜索;priority_queue
根据优先级处理元素,适合任务调度和图算法。在使用这些容器时,需要根据具体的应用场景选择合适的容器,以便最大化性能和代码简洁性。每种容器都有其特定的用途和优势,合理选择可以有效简化程序设计,并提升程序的可维护性和效率。