C++语言原理与实践(十三):stack、queue 和 priority_queue的介绍与使用

本篇目标:

学会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.有效的括号

网址:

20. 有效的括号 - 力扣(LeetCode)20. 有效的括号 - 给定一个只包括 '(',')','{','}','','' 的字符串 s ,判断字符串是否有效。有效字符串需满足: 1. 左括号必须用相同类型的右括号闭合。 2. 左括号必须以正确的顺序闭合。 3. 每个右括号都有一个对应的相同类型的左括号。 示例 1:输入:s = "()"输出:true示例 2:输入:s = "()\[\]{}"输出:true示例 3:输入:s = "(]"输出:false示例 4:输入:s = "(\[\])"输出:true示例 5:输入:s = "()"输出:false 提示: * 1 <= s.length <= 104 * s 仅由括号 '()\[\]{}' 组成https://leetcode.cn/problems/valid-parentheses/<1>.题意解释

给定一个只包含 (、)、[、]、{、} 的字符串,判断其中的括号是否正确匹配。

例如:

cpp 复制代码
"()[]{}"  → true
"([{}])"  → true
"(]"      → false
"([)]"    → false

<2>.解题思路

括号匹配需要满足:

  1. 左括号和右括号的类型必须相同。
  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.用队列实现栈

225. 用队列实现栈 - 力扣(LeetCode)225. 用队列实现栈 - 请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。实现 MyStack 类: * void push(int x) 将元素 x 压入栈顶。 * int pop() 移除并返回栈顶元素。 * int top() 返回栈顶元素。 * boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。 注意: * 你只能使用队列的标准操作 ------ 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。 * 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。 示例:输入:"MyStack", "push", "push", "top", "pop", "empty"\[, 1, 2, \[\], \[\], \[\]]输出:null, null, null, 2, 2, false解释:MyStack myStack = new MyStack();myStack.push(1);myStack.push(2);myStack.top(); // 返回 2myStack.pop(); // 返回 2myStack.empty(); // 返回 False 提示: * 1 <= x <= 9 * 最多调用100 次 push、pop、top 和 empty * 每次调用 pop 和 top 都保证栈不为空 进阶:你能否仅用一个队列来实现栈。https://leetcode.cn/problems/implement-stack-using-queues/

<1>.题目解析

只使用队列实现栈的:

cpp 复制代码
push(x)
pop()
top()
empty()

<2>.解题思路

队列是先进先出,栈是后进先出。

准备两个队列:

cpp 复制代码
queue<int> q1;
queue<int> q2;

规定 q1.front() 始终表示栈顶。

插入新元素 x 时:

  1. 先把 x 放入空队列 q2;
  2. 将 q1 中原来的元素依次转移到 q2;
  3. 交换 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大个最大元素

215. 数组中的第K个最大元素 - 力扣(LeetCode)215. 数组中的第K个最大元素 - 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1:输入: 3,2,1,5,6,4, k = 2输出: 5示例 2:输入: 3,2,3,1,2,4,5,5,6, k = 4输出: 4 提示: * 1 <= k <= nums.length <= 105 * -104 <= numsi <= 104https://leetcode.cn/problems/kth-largest-element-in-an-array/

<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> 默认是大堆,堆顶始终是当前最大元素。

处理步骤:

  1. 将数组中的所有元素放入大堆;
  2. 弹出前 k-1 个最大元素;
  3. 此时堆顶就是第 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() 都只删除元素,不返回元素;访问或删除元素前,应保证容器非空。掌握它们各自的处理顺序和常用接口,才能根据具体问题选择合适的工具。

相关推荐
Shaoshing1 小时前
消息发送
java·开发语言
乌暮2 小时前
从 `println` 到生产级日志:Spring Boot 日志体系完全指南
java·开发语言·jvm·spring boot·后端·学习·面试
星夜离尘562 小时前
MATLAB 定点量化误差实验 半个 LSB 的界限为什么挡不住削顶失真
开发语言·matlab
TDTC_XQ2 小时前
模板类的写法
linux·c++
Wang's Blog2 小时前
Java 项目实战: 交友项目 - 百度人脸识别:介绍
java·开发语言·交友
障碍的枫子2 小时前
我的第一个Agent
开发语言·python·langchain
Pniubi2 小时前
区分agent中长期记忆和偏好记忆
开发语言·ai·智能体
我就是不信3 小时前
Swift 方法派发机制详解
开发语言·ios·swift
CCCCCCCCharlie3 小时前
深入解析I/O底层:文件描述符与缓冲区真相
linux·开发语言·c++·算法