本篇目标:
学会stack,queue和priority_queue的接口使用
引子:stack、queue 和 priority_queue 都属于容器适配器。它们不直接负责存储数据,而是在其
他容器的基础上封装出一组特定接口。
cpp
#include <stack>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
一.stack
1. 基本特点
stack 是栈,遵循后进先出原则,即 LIFO(Last In First Out)。
只能直接访问栈顶元素:
cpp
入栈:1 2 3
出栈:3 2 1
默认情况下,stack 使用 deque 作为底层容器。
2. 常用接口
| 接口 | 功能 | 时间复杂度 |
|---|---|---|
stack<T> st |
构造一个空栈 | O(1) |
st.empty() |
判断栈是否为空 | O(1) |
st.size() |
返回元素个数 | O(1) |
st.top() |
访问栈顶元素 | O(1) |
st.push(x) |
将元素压入栈顶 | O(1) |
st.pop() |
删除栈顶元素 | O(1) |
基本使用:
cpp
#include <iostream>
#include <stack>
using namespace std;
int main()
{
stack<int> st;
st.push(10);
st.push(20);
st.push(30);
while(!st.empty())
{
cout<<st.top()<<endl;
st.pop();
}
return 0;
}
3. 使用特点
pop() 只删除,不返回元素
下面的写法是错误的:
cpp
int value = st.pop(); // 错误
正确写法:
cpp
int value = st.top();
st.pop();
访问栈顶前应先判断是否为空
cpp
if (!st.empty())
{
cout << st.top() << endl;
}
空栈调用 top() 或 pop() 会产生未定义行为。
4.有效的括号
网址:
给定一个只包含 (、)、[、]、{、} 的字符串,判断其中的括号是否正确匹配。
例如:
cpp
"()[]{}" → true
"([{}])" → true
"(]" → false
"([)]" → false
<2>.解题思路
括号匹配需要满足:
- 左括号和右括号的类型必须相同。
- 后出现的左括号必须先匹配。
例如:
cpp
([{}])
左括号进入顺序:
cpp
( → [ → {
右括号出现后,匹配顺序必须是:
} → ] → )
这正好符合栈"后进先出"的特点。
具体过程:
- 遇到左括号,将其压入栈中。
- 遇到右括号,检查栈顶左括号是否与其匹配。
- 如果栈为空,说明右括号没有对应的左括号,返回
false。 - 如果类型不匹配,返回
false。 - 如果匹配,就将栈顶左括号弹出。
- 遍历结束后,栈必须为空,否则说明存在未匹配的左括号。
<3>.示例分析
以字符串:
cpp
"([{}])"
为例:
| 当前字符 | 操作 | 栈中内容 |
|---|---|---|
( |
左括号入栈 | ( |
[ |
左括号入栈 | ([ |
{ |
左括号入栈 | ([{ |
} |
与 { 匹配,出栈 |
([ |
] |
与 [ 匹配,出栈 |
( |
) |
与 ( 匹配,出栈 |
空 |
遍历结束后栈为空,因此括号匹配成功。
两种失败情况
1.右括号多
cpp
"())"
处理最后一个 ) 时,栈已经为空,匹配失败。
2.左括号多
cpp
"(()"
遍历结束后栈中还有一个 (,匹配失败。
因此最后不能直接返回 true,必须返回:
cpp
return st.empty();
<4>.代码实现
cpp
class Solution
{
public:
bool isValid(string s)
{
stack<char> st;
for (char ch : s)
{
// 遇到左括号,直接入栈
if (ch == '(' || ch == '[' || ch == '{')
{
st.push(ch);
}
else
{
// 右括号过多
if (st.empty())
{
return false;
}
char top = st.top();
// 判断括号类型是否匹配
if ((ch == ')' && top != '(') ||
(ch == ']' && top != '[') ||
(ch == '}' && top != '{'))
{
return false;
}
// 当前左右括号匹配
st.pop();
}
}
// 栈为空,说明所有左括号都完成匹配
return st.empty();
}
};
二.queue
1. 基本特点
queue 是队列,遵循先进先出原则,即 FIFO(First In First Out)。
元素从队尾进入,从队头离开:
cpp
入队:1 2 3
出队:1 2 3
默认情况下,queue 使用 deque 作为底层容器。
2. 常用接口
|--------------|----------|-------|
| 接口 | 功能 | 时间复杂度 |
| queue<T> q | 构造一个空队列 | O(1) |
| q.empty() | 判断队列是否为空 | O(1) |
| q.size() | 返回元素个数 | O(1) |
| q.front() | 访问队头元素 | O(1) |
| q.back() | 访问队尾元素 | O(1) |
| q.push(x) | 从队尾插入元素 | O(1) |
| q.pop() | 删除队头元素 | O(1) |
基本使用:
cpp
#include <iostream>
#include <queue>
using namespace std;
int main()
{
queue<int> q;
q.push(10);
q.push(20);
q.push(30);
while(!q.empty())
{
cout<<q.front()<<" ";
q.pop();
}
return 0;
}
3. 使用特点
队头和队尾的作用不同
cpp
q.front(); // 下一个将要出队的元素
q.back(); // 最后一个进入队列的元素
pop() 只删除队头,不返回元素
cpp
int value = q.front();
q.pop();
访问元素前要判断队列是否为空
cpp
if (!q.empty())
{
cout << q.front() << endl;
}
4.用队列实现栈
<1>.题目解析
只使用队列实现栈的:
cpp
push(x)
pop()
top()
empty()
<2>.解题思路
队列是先进先出,栈是后进先出。
准备两个队列:
cpp
queue<int> q1;
queue<int> q2;
规定 q1.front() 始终表示栈顶。
插入新元素 x 时:
- 先把
x放入空队列q2; - 将
q1中原来的元素依次转移到q2; - 交换
q1和q2。
例如,原来的栈为:
cpp
栈顶 -> 3 2 1
对应队列:
cpp
队头 -> 3 2 1
插入 4 时:
cpp
q2:4
然后把 q1 中的元素转移过来:
cpp
q2:4 3 2 1
所以新元素 4 位于队头,也就相当于栈顶。
<3>.代码实现
cpp
class MyStack
{
public:
void push(int x)
{
// 新元素先进入辅助队列
q2.push(x);
// 将原来的元素移动到新元素后面
while (!q1.empty())
{
q2.push(q1.front());
q1.pop();
}
// q1继续作为主队列
swap(q1, q2);
}
int pop()
{
int value = q1.front();
q1.pop();
return value;
}
int top()
{
return q1.front();
}
bool empty()
{
return q1.empty();
}
private:
queue<int> q1;
queue<int> q2;
};
三.priority_queue
1. 基本特点
priority_queue 称为优先队列。
普通队列按照进入顺序出队,优先队列则按照元素的优先级出队。默认情况下,数值越大的元素优先级越高。
priority_queue 的底层结构是堆,默认使用 vector 存储数据。
cpp
插入顺序:3 1 8 5
弹出顺序:8 5 3 1
2. 常用接口
|------------------------|------------|----------|
| 接口 | 功能 | 时间复杂度 |
| priority_queue<T> pq | 构造一个空优先队列 | O(1) |
| pq.empty() | 判断是否为空 | O(1) |
| pq.size() | 返回元素个数 | O(1) |
| pq.top() | 访问优先级最高的元素 | O(1) |
| pq.push(x) | 插入元素 | O(log N) |
| pq.pop() | 删除优先级最高的元素 | O(log N) |
3. 大堆与小堆
priority_queue默认创建大堆
堆顶是最大元素:
cpp
priority_queue<int> pq;
pq.push(3);
pq.push(8);
pq.push(1);
cout << pq.top() << endl; // 8
使用 greater 创建小堆
堆顶是最小元素:
cpp
priority_queue<int, vector<int>, greater<int>> pq;
pq.push(3);
pq.push(8);
pq.push(1);
cout << pq.top() << endl; // 1
小堆需要包含:
cpp
#include <functional>
模板参数含义:
cpp
priority_queue<
int, // 元素类型
vector<int>, // 底层容器
greater<int> // 比较方式
> pq;
可以简单记忆为:
cpp
priority_queue<int> // 大堆
priority_queue<int, vector<int>, greater<int>> // 小堆
4. 使用特点
top() 表示优先级最高的元素
它不一定是最先插入的元素:
cpp
priority_queue<int> pq;
pq.push(10);
pq.push(50);
pq.push(20);
cout << pq.top() << endl; // 50
pop() 同样不返回元素
cpp
int value = pq.top();
pq.pop();
不支持遍历和随机访问
priority_queue 不提供迭代器,也不能使用下标访问。只能不断读取并删除堆顶:
cpp
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
5.数组中的第K大个最大元素
<1>.题目解析
给定整数数组 nums 和整数 k,返回数组中第 k 个最大的元素。
注意,是排序后的第 k 大,不是第 k 个不同的元素。
例如:
cpp
nums = [3,2,1,5,6,4]
k = 2
排序后为:
cpp
6 5 4 3 2 1
第二大的元素是 5。
<2>.思路
priority_queue<int> 默认是大堆,堆顶始终是当前最大元素。
处理步骤:
- 将数组中的所有元素放入大堆;
- 弹出前
k-1个最大元素; - 此时堆顶就是第
k大的元素。
例如:
cpp
堆顶弹出顺序:6、5、4、3、2、1
当 k = 2 时,只需要弹出最大的 6,剩下的堆顶 5 就是答案。
<3>.代码
cpp
class Solution
{
public:
int findKthLargest(vector<int>& nums, int k)
{
// 使用数组区间直接构造大堆
priority_queue<int> pq(nums.begin(), nums.end());
// 删除前k-1个最大元素
for (int i = 0; i < k - 1; ++i)
{
pq.pop();
}
// 此时堆顶就是第k大的元素
return pq.top();
}
};
也可以逐个插入:
cpp
priority_queue<int> pq;
for (int value : nums)
{
pq.push(value);
}
总结:
stack、queue 和 priority_queue 都属于容器适配器,它们在底层容器的基础上,提供不同的元素访问与删除规则。
stack 遵循后进先出原则,通过 top() 访问栈顶,适合解决括号匹配等问题;queue 遵循先进先出原则,通过 front() 访问队头,适合按顺序处理数据;priority_queue 按优先级取出元素,默认使用大堆,也可以通过 greater 创建小堆,适合求最大值、最小值以及处理 Top K 问题。
使用时需要注意:三者的 pop() 都只删除元素,不返回元素;访问或删除元素前,应保证容器非空。掌握它们各自的处理顺序和常用接口,才能根据具体问题选择合适的工具。