目录
[📚 一、容器适配器概述](#📚 一、容器适配器概述)
[1.1 什么是容器适配器?](#1.1 什么是容器适配器?)
[1.2 核心特点](#1.2 核心特点)
[🗃️ 二、stack(栈)](#🗃️ 二、stack(栈))
[2.1 栈的基本概念](#2.1 栈的基本概念)
[2.2 栈的接口](#2.2 栈的接口)
[2.3 栈的经典应用](#2.3 栈的经典应用)
[2.3.1 最小栈(MinStack)](#2.3.1 最小栈(MinStack))
[2.3.2 栈的弹出/压入序列](#2.3.2 栈的弹出/压入序列)
[2.3.3 逆波兰表达式求值](#2.3.3 逆波兰表达式求值)
[2.4 stack的模拟实现](#2.4 stack的模拟实现)
[🚶 三、queue(队列)](#🚶 三、queue(队列))
[3.1 队列的基本概念](#3.1 队列的基本概念)
[3.2 队列的接口](#3.2 队列的接口)
[3.3 经典应用:用队列实现栈](#3.3 经典应用:用队列实现栈)
[3.4 queue的模拟实现](#3.4 queue的模拟实现)
[🏆 四、priority_queue(优先级队列)](#🏆 四、priority_queue(优先级队列))
[4.1 优先级队列的基本概念](#4.1 优先级队列的基本概念)
[4.2 priority_queue的接口](#4.2 priority_queue的接口)
[4.3 使用示例](#4.3 使用示例)
[4.4 自定义类型在priority_queue中的使用](#4.4 自定义类型在priority_queue中的使用)
[4.5 经典应用:数组中第K个最大元素](#4.5 经典应用:数组中第K个最大元素)
[4.6 priority_queue的模拟实现(简化版)](#4.6 priority_queue的模拟实现(简化版))
[🔄 五、底层容器与deque](#🔄 五、底层容器与deque)
[5.1 为什么选择deque作为默认底层容器?](#5.1 为什么选择deque作为默认底层容器?)
[5.2 deque的结构特点](#5.2 deque的结构特点)
[5.3 不同底层容器的选择](#5.3 不同底层容器的选择)
[🎯 六、选择指南与最佳实践](#🎯 六、选择指南与最佳实践)
[6.1 何时使用stack?](#6.1 何时使用stack?)
[6.2 何时使用queue?](#6.2 何时使用queue?)
[6.3 何时使用priority_queue?](#6.3 何时使用priority_queue?)
[6.4 性能对比](#6.4 性能对比)
[📝 七、总结](#📝 七、总结)
📚 一、容器适配器概述
1.1 什么是容器适配器?
容器适配器是一种设计模式,它将一个类的接口转换成用户希望的另一个接口。在C++ STL中,stack、queue和priority_queue都属于容器适配器,它们基于现有的容器(如vector、list、deque)提供特定功能。
1.2 核心特点
-
不提供完整的容器功能,只提供特定操作
-
基于现有容器实现,复用底层数据结构
-
隐藏底层实现细节,提供简洁的接口
🗃️ 二、stack(栈)
2.1 栈的基本概念
栈是一种**后进先出(LIFO)**的数据结构,只允许在栈顶进行插入和删除操作。
2.2 栈的接口
| 函数声明 | 接口说明 |
|---|---|
stack() |
构造空的栈 |
empty() |
检测栈是否为空 |
size() |
返回栈中元素的个数 |
top() |
返回栈顶元素的引用 |
push(const T& val) |
将元素val压入栈中 |
pop() |
将栈顶元素弹出 |
2.3 栈的经典应用
2.3.1 最小栈(MinStack)
cpp
class MinStack {
public:
void push(int x) {
// 保存所有元素
_elem.push(x);
// 如果x小于等于_min栈顶元素,压入_min栈
if(_min.empty() || x <= _min.top())
_min.push(x);
}
void pop() {
// 如果出栈元素等于_min栈顶元素,_min也出栈
if(_min.top() == _elem.top())
_min.pop();
_elem.pop();
}
int top() { return _elem.top(); }
int getMin() { return _min.top(); }
private:
stack<int> _elem; // 保存所有元素
stack<int> _min; // 保存最小值
};
2.3.2 栈的弹出/压入序列
cpp
bool IsPopOrder(vector<int> pushV, vector<int> popV) {
if(pushV.size() != popV.size()) return false;
stack<int> s;
int pushIdx = 0;
for(int popIdx = 0; popIdx < popV.size(); popIdx++) {
// 当栈为空或栈顶不等于弹出元素时,压入元素
while(s.empty() || s.top() != popV[popIdx]) {
if(pushIdx < pushV.size())
s.push(pushV[pushIdx++]);
else
return false;
}
s.pop(); // 栈顶等于弹出元素,出栈
}
return true;
}
2.3.3 逆波兰表达式求值
cpp
int evalRPN(vector<string>& tokens) {
stack<int> s;
for(const auto& token : tokens) {
if(token == "+" || token == "-" || token == "*" || token == "/") {
int right = s.top(); s.pop();
int left = s.top(); s.pop();
if(token == "+") s.push(left + right);
else if(token == "-") s.push(left - right);
else if(token == "*") s.push(left * right);
else if(token == "/") s.push(left / right);
} else {
s.push(stoi(token));
}
}
return s.top();
}
2.4 stack的模拟实现
cpp
#include <vector>
#include <deque>
namespace my {
template<class T, class Container = std::deque<T>>
class stack {
public:
stack() {}
void push(const T& x) { _c.push_back(x); }
void pop() { _c.pop_back(); }
T& top() { return _c.back(); }
const T& top() const { return _c.back(); }
size_t size() const { return _c.size(); }
bool empty() const { return _c.empty(); }
private:
Container _c;
};
}
🚶 三、queue(队列)
3.1 队列的基本概念
队列是一种**先进先出(FIFO)**的数据结构,元素从队尾入队,从队头出队。
3.2 队列的接口
| 函数声明 | 接口说明 |
|---|---|
queue() |
构造空的队列 |
empty() |
检测队列是否为空 |
size() |
返回队列中有效元素的个数 |
front() |
返回队头元素的引用 |
back() |
返回队尾元素的引用 |
push(const T& val) |
在队尾将元素val入队列 |
pop() |
将队头元素出队列 |
3.3 经典应用:用队列实现栈
cpp
class MyStack {
private:
queue<int> q;
public:
void push(int x) {
int n = q.size();
q.push(x);
// 将前面的元素重新入队,使得新元素在队头
for(int i = 0; i < n; i++) {
q.push(q.front());
q.pop();
}
}
void pop() { q.pop(); }
int top() { return q.front(); }
bool empty() { return q.empty(); }
};
3.4 queue的模拟实现
cpp
#include <list>
#include <deque>
namespace my {
template<class T, class Container = std::deque<T>>
class queue {
public:
queue() {}
void push(const T& x) { _c.push_back(x); }
void pop() { _c.pop_front(); }
T& front() { return _c.front(); }
const T& front() const { return _c.front(); }
T& back() { return _c.back(); }
const T& back() const { return _c.back(); }
size_t size() const { return _c.size(); }
bool empty() const { return _c.empty(); }
private:
Container _c;
};
}
🏆 四、priority_queue(优先级队列)
4.1 优先级队列的基本概念
优先级队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的(默认大顶堆)。
4.2 priority_queue的接口
| 函数声明 | 接口说明 |
|---|---|
priority_queue() |
构造空的优先级队列 |
priority_queue(first, last) |
用迭代器范围构造优先级队列 |
empty() |
检测优先级队列是否为空 |
top() |
返回优先级队列中最大(最小)元素 |
push(x) |
在优先级队列中插入元素x |
pop() |
删除优先级队列中最大(最小)元素 |
4.3 使用示例
cpp
#include <queue>
#include <vector>
#include <functional> // for greater
void TestPriorityQueue() {
vector<int> v{3, 2, 7, 6, 0, 4, 1, 9, 8, 5};
// 默认大顶堆
priority_queue<int> maxHeap;
for(auto& e : v) maxHeap.push(e);
cout << "大顶堆堆顶: " << maxHeap.top() << endl; // 9
// 小顶堆
priority_queue<int, vector<int>, greater<int>> minHeap(v.begin(), v.end());
cout << "小顶堆堆顶: " << minHeap.top() << endl; // 0
}
4.4 自定义类型在priority_queue中的使用
cpp
class Date {
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year), _month(month), _day(day) {}
bool operator<(const Date& d) const {
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
bool operator>(const Date& d) const {
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._day && _day > d._day);
}
friend ostream& operator<<(ostream& _cout, const Date& d) {
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
private:
int _year, _month, _day;
};
void TestDatePriorityQueue() {
// 大顶堆,需要提供 operator<
priority_queue<Date> maxHeap;
maxHeap.push(Date(2018, 10, 29));
maxHeap.push(Date(2018, 10, 28));
maxHeap.push(Date(2018, 10, 30));
cout << "日期大顶堆堆顶: " << maxHeap.top() << endl; // 2018-10-30
// 小顶堆,需要提供 operator>
priority_queue<Date, vector<Date>, greater<Date>> minHeap;
minHeap.push(Date(2018, 10, 29));
minHeap.push(Date(2018, 10, 28));
minHeap.push(Date(2018, 10, 30));
cout << "日期小顶堆堆顶: " << minHeap.top() << endl; // 2018-10-28
}
4.5 经典应用:数组中第K个最大元素
cpp
int findKthLargest(vector<int>& nums, int k) {
// 创建小顶堆,保持堆的大小为k
priority_queue<int, vector<int>, greater<int>> minHeap;
for(int num : nums) {
minHeap.push(num);
if(minHeap.size() > k) {
minHeap.pop(); // 删除最小的元素
}
}
return minHeap.top(); // 堆顶就是第K个最大元素
}
4.6 priority_queue的模拟实现(简化版)
cpp
#include <vector>
#include <functional>
namespace my {
template<class T, class Container = vector<T>,
class Compare = less<typename Container::value_type>>
class priority_queue {
public:
priority_queue() = default;
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
: _c(first, last) {
make_heap(_c.begin(), _c.end(), _comp);
}
void push(const T& x) {
_c.push_back(x);
push_heap(_c.begin(), _c.end(), _comp);
}
void pop() {
pop_heap(_c.begin(), _c.end(), _comp);
_c.pop_back();
}
const T& top() const { return _c.front(); }
bool empty() const { return _c.empty(); }
size_t size() const { return _c.size(); }
private:
Container _c;
Compare _comp;
};
}
🔄 五、底层容器与deque
5.1 为什么选择deque作为默认底层容器?
STL中stack和queue默认使用deque作为底层容器,主要原因是:
-
效率平衡:deque在头尾插入/删除都是O(1)时间复杂度
-
内存效率:不需要像vector那样大量搬移元素
-
空间利用率:比list更高效,不需要额外指针开销
-
避免遍历:stack和queue不需要遍历操作,deque遍历效率低的缺点不影响
5.2 deque的结构特点
deque(双端队列)不是真正连续的空间,而是由分段连续的小空间拼接而成:
-
支持快速随机访问
-
头尾插入删除效率高
-
空间利用率高
-
迭代器设计复杂
5.3 不同底层容器的选择
cpp
// stack可以使用vector、list、deque
stack<int, vector<int>> s1; // 适合随机访问,尾部操作
stack<int, list<int>> s2; // 适合频繁插入删除
stack<int, deque<int>> s3; // 默认选择,综合性能好
// queue可以使用list、deque(不能用vector,因为需要pop_front)
queue<int, list<int>> q1; // 适合频繁插入删除
queue<int, deque<int>> q2; // 默认选择
🎯 六、选择指南与最佳实践
6.1 何时使用stack?
-
需要后进先出(LIFO)的场景
-
括号匹配、表达式求值
-
函数调用栈、撤销操作
-
深度优先搜索(DFS)
6.2 何时使用queue?
-
需要先进先出(FIFO)的场景
-
广度优先搜索(BFS)
-
任务调度、消息队列
-
缓冲区管理
6.3 何时使用priority_queue?
-
需要快速获取最大/最小元素的场景
-
任务优先级调度
-
求第K大/第K小元素
-
Dijkstra算法等图算法
6.4 性能对比
| 操作 | stack | queue | priority_queue |
|---|---|---|---|
| 插入 | O(1) | O(1) | O(log n) |
| 删除 | O(1) | O(1) | O(log n) |
| 访问顶部 | O(1) | O(1) | O(1) |
| 查找元素 | 不支持 | 不支持 | 不支持 |
📝 七、总结
C++ STL中的容器适配器(stack、queue、priority_queue)为我们提供了高效、易用的数据结构实现:
-
stack:专注于后进先出操作,适合需要"撤销"或"回退"的场景
-
queue:管理先进先出序列,适合任务调度和缓冲区
-
priority_queue:基于堆实现,快速获取最大/最小元素
这些容器适配器的设计体现了单一职责原则 和适配器模式的精髓,它们:
-
提供简洁的接口,隐藏复杂实现
-
基于现有容器,代码复用性高
-
通过模板参数灵活选择底层容器
-
在实际开发中广泛应用,提升代码质量
理解这些容器的底层实现、适用场景和性能特点,能够帮助我们在实际编程中做出更合适的选择,编写出更高效、更易维护的代码。