当前位置: 首页 > news >正文

深度学习C++中的数据结构:栈和队列

一、栈(Stack):先进后出的"叠盘子"

栈的核心特点是先进后出(LIFO,Last In First Out),就像叠盘子一样:最后放上去的盘子,要先拿下来;最底下的盘子,只能等上面的都拿完才能取。

image.png

image.png

1. 栈的定义方式

栈有两种常见定义方式,核心是指定存储的数据类型底层容器(默认用deque)。

定义方式

代码示例

说明

普通定义(默认底层容器)

stack<int> st1;

存储int类型,底层默认用deque容器

指定底层容器

stack<int, vector<int>> st2; stack<int, list<int>> st3;

分别指定vector、list作为底层容器

注意:如果不指定底层容器,C++标准库会默认用deque——因为deque兼顾了vector的随机访问和list的高效插入删除,适合栈的操作场景。

2. 栈的常用操作

栈的操作很简洁,核心围绕"栈顶"(毕竟只能从栈顶进出),常用成员函数如下:

成员函数

功能

示例

empty()

判断栈是否为空,返回bool值

if (st.empty()) { ... }

size()

获取栈中有效元素的个数,返回整数

cout << st.size();

top()

获取栈顶元素(不删除),注意栈空时调用会报错

int val = st.top();

push(x)

将元素x压入栈顶

st.push(10);

pop()

删除栈顶元素(不返回),栈空时调用会报错

st.pop();

swap(st)

交换两个栈的所有元素

st1.swap(st2);

3. 栈的使用示例

下面代码演示了栈的基本操作:压入4个元素,然后从栈顶依次弹出并打印。

代码语言:cpp

代码运行次数:0

运行

AI代码解释

#include <iostream>
#include <stack>  // 包含stack头文件
#include <vector> // 若指定vector为底层容器,需包含此头文件using namespace std;int main() {// 定义一个以vector为底层容器的栈,存储int类型stack<int, vector<int>> st;// 压入元素(栈顶依次变成1→2→3→4)st.push(1);st.push(2);st.push(3);st.push(4);// 打印栈的大小(此时有4个元素)cout << "栈的大小:" << st.size() << endl; // 输出:4// 循环弹出栈顶元素(直到栈空)while (!st.empty()) {// 先获取栈顶元素并打印cout << st.top() << " "; // 再删除栈顶元素(注意:pop()不返回元素,必须先top()再pop())st.pop(); }cout << endl; // 输出:4 3 2 1(符合先进后出)return 0;
}

二、队列(Queue):先进先出的"排队"

队列的核心特点是先进先出(FIFO,First In First Out),就像日常生活中排队买东西:先排队的人先结账,后排队的人只能等前面的人结束才能轮到。

image.png

image.png

1. 队列的定义方式

和栈类似,队列也需要指定数据类型底层容器(默认同样用deque)。

定义方式

代码示例

说明

普通定义(默认底层容器)

queue<int> q1;

存储int类型,底层默认用deque

指定底层容器

queue<int, vector<int>> q2; queue<int, list<int>> q3;

分别指定vector、list作为底层容器

注意:虽然可以用vector作为底层容器,但队列的pop()操作需要删除队头元素——vector删除队头元素效率很低(要移动所有元素),所以实际中更推荐用list或默认的deque。

2. 队列的常用操作

队列的操作围绕"队头"(出队)和"队尾"(入队),常用成员函数如下:

成员函数

功能

示例

empty()

判断队列是否为空,返回bool值

if (q.empty()) { ... }

size()

获取队列中有效元素的个数

cout << q.size();

front()

获取队头元素(不删除),队列空时调用报错

int val = q.front();

back()

获取队尾元素(不删除),队列空时调用报错

int val = q.back();

push(x)

将元素x插入队尾

q.push(10);

pop()

删除队头元素(不返回),队列空时调用报错

q.pop();

swap(q)

交换两个队列的所有元素

q1.swap(q2);

3. 队列的使用示例

下面代码演示了队列的基本操作:插入4个元素,然后从队头依次弹出并打印。

代码语言:cpp

代码运行次数:0

运行

AI代码解释

#include <iostream>
#include <queue>  // 包含queue头文件
#include <list>   // 若指定list为底层容器,需包含此头文件using namespace std;int main() {// 定义一个以list为底层容器的队列,存储int类型queue<int, list<int>> q;// 插入元素(队尾依次加入1→2→3→4,队头始终是1)q.push(1);q.push(2);q.push(3);q.push(4);// 打印队列的大小(此时有4个元素)cout << "队列的大小:" << q.size() << endl; // 输出:4// 循环弹出队头元素(直到队空)while (!q.empty()) {// 先获取队头元素并打印cout << q.front() << " "; // 再删除队头元素q.pop(); }cout << endl; // 输出:1 2 3 4(符合先进先出)return 0;
}

三、实战:用栈解决逆波兰表达式求值

栈的一个经典应用是计算逆波兰表达式(也叫后缀表达式)。先搞懂什么是中缀、后缀表达式:

1. 中缀表达式 vs 后缀表达式
  • 中缀表达式:我们平时写的1 + 2 * 3,操作符在两个操作数中间,需要考虑优先级(先乘后加);
  • 后缀表达式:把操作符放在两个操作数后面,比如1 2 3 * +,不需要考虑优先级,按顺序计算即可。

image.png

image.png

后缀表达式的计算规则很简单:遇到操作数就入栈,遇到操作符就弹出栈顶两个元素,计算后将结果再入栈,最终栈里剩下的就是结果。

比如计算1 2 3 * +

  1. 1、2、3依次入栈 → 栈:1, 2, 3;
  2. 遇到*,弹出3(右操作数)和2(左操作数),计算2*3=6,6入栈 → 栈:1, 6;
  3. 遇到+,弹出6和1,计算1+6=7,7入栈 → 栈:7;
  4. 最终结果就是7。

image.png

image.png

2. 力扣例题:逆波兰表达式求值

题目链接:150. 逆波兰表达式求值 - 力扣(LeetCode)

题目描述

给你一个字符串数组tokens,表示一个逆波兰表达式,返回该表达式的计算结果。操作符只有+-*/,且所有操作数都是整数。

解题思路

完全遵循后缀表达式的计算规则,用栈实现:

  1. 遍历字符串数组tokens
  2. 若当前字符串是操作数(不是+-*/),转成整数后入栈;
  3. 若当前字符串是操作符,弹出栈顶两个元素(注意:先弹的是右操作数,后弹的是左操作数);
  4. 计算两个元素的结果,将结果入栈;
  5. 遍历结束后,栈中仅剩的一个元素就是最终结果。

image.png

image.png

代码实现

代码语言:cpp

代码运行次数:0

运行

AI代码解释

#include <iostream>
#include <vector>
#include <stack>
#include <string>using namespace std;class Solution {
public:int evalRPN(vector<string>& tokens) {// 1. 定义一个栈,存储操作数和中间结果stack<int> st;// 2. 遍历字符串数组for (auto& str : tokens) {// 2.1 判断当前字符串是否为操作符if (str == "+" || str == "-" || str == "*" || str == "/") {// 弹出栈顶两个元素(注意顺序:先弹右操作数,后弹左操作数)int right = st.top(); // 右操作数(后入栈的)st.pop();int left = st.top();  // 左操作数(先入栈的)st.pop();// 根据操作符计算结果,结果入栈switch (str[0]) { // str是单个字符的字符串,取第一个字符判断case '+':st.push(left + right);break;case '-':st.push(left - right); // 注意:左减右,不是右减左break;case '*':st.push(left * right);break;case '/':st.push(left / right); // 注意:左除右,且题目保证是整数除法break;}} else {// 2.2 是操作数,转成整数后入栈(stoi:string to int)st.push(stoi(str));}}// 3. 遍历结束,栈中仅剩的元素就是结果return st.top();}
};// 测试代码
int main() {Solution sol;// 示例:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]vector<string> tokens = {"10","6","9","3","+","-11","*","/","*","17","+","5","+"};cout << "逆波兰表达式结果:" << sol.evalRPN(tokens) << endl; // 输出:22return 0;
}

四、底层原理:为什么栈和队列是"容器适配器"?

前面我们反复提到"容器适配器",到底什么是适配器?

简单说:适配器是一种"包装器",它基于已有的容器(如vector、list、deque),通过限制部分操作、封装特定接口,实现新的数据结构功能

栈和队列本身不存储数据,而是"借用"底层容器的存储空间,只暴露符合自身逻辑的接口(比如栈只暴露栈顶操作,队列只暴露队头/队尾操作)。

image.png

image.png

1. 栈的底层实现逻辑

栈的核心是"先进后出",所以无论用哪种底层容器,只需确保所有操作都针对容器的"一端"(比如vector的尾端、list的尾端):

  • push():调用底层容器的push_back()(在尾端插入);
  • pop():调用底层容器的pop_back()(删除尾端元素);
  • top():调用底层容器的back()(获取尾端元素)。

image.png

image.png

image.png

image.png

2. 队列的底层实现逻辑

队列的核心是"先进先出",所以需要确保入队在一端,出队在另一端

  • push():调用底层容器的push_back()(队尾插入);
  • pop():调用底层容器的pop_front()(队头删除);
  • front():调用底层容器的front()(获取队头);
  • back():调用底层容器的back()(获取队尾)。

这也是为什么不推荐用vector作为队列的底层容器——vector没有pop_front()接口(删除队头元素需要移动所有元素,效率低),而list和deque都有高效的pop_front()

image.png

image.png

image.png

image.png

3. 容器适配器的设计思想

容器适配器的设计体现了C++的"复用"思想:不需要重新开发存储逻辑,而是基于已有的高效容器,通过封装接口实现新的数据结构。

除了栈和队列,C++中还有priority_queue(优先队列)也是容器适配器,底层默认用vector,通过堆排序实现"每次出队最大/最小元素"的逻辑。

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

总结

  1. :先进后出,操作围绕栈顶,默认底层容器是deque;
  2. 队列:先进先出,操作围绕队头/队尾,默认底层容器是deque;
  3. 核心应用:栈适合解决"后入先出"场景(如逆波兰表达式、括号匹配),队列适合解决"先入先出"场景(如任务调度、广度优先搜索);
  4. 底层本质:容器适配器,基于现有容器封装接口,复用存储逻辑。

掌握栈和队列的核心逻辑后,很多算法题都会变得简单——比如括号匹配、滑动窗口最大值、二叉树的层序遍历等,后续可以继续深入练习这些场景~

http://www.dtcms.com/a/528506.html

相关文章:

  • 2025-tomcat web实践
  • 免费建立微信网站如何设计的英文网站
  • liferay 做网站哪里有网站开发公司
  • Leetcode 38
  • Django 学习路线图
  • 把网站放到服务器公司做网站需要准备什么资料
  • 如何批量获取蛋白质序列的所有结构域(domain)数据-2
  • MySQL基础知识大全
  • 站群服务器都有什么作用
  • C# 初级编程
  • linux fair调度器
  • 建设工程项目在哪个网站查询零食网站页面模板
  • Python 基础详解:enumerate() 函数
  • 基于鸿蒙UniProton的车载信息娱乐系统开发指南
  • 自然语言处理前沿创新方向与技术路径
  • 微软做网页的软件烟台优化网站公司
  • 使用Jmeter进行http接口测试
  • Jenkins从节点配置全攻略:从搭建到任务调度,参数详解与实战指南
  • 【Agentic AI】提示链模式学习笔记
  • 广东省省考备考(第一百三十三天10.25)——科学推理(强化训练)
  • 四季变化,室外太阳光的剧烈变化, 从“被动适应”到“主动免疫”,机器视觉检测如何实现?
  • 观察者模式学习
  • 常用标准电阻表 | E24标准电阻表
  • 三、检索增强生成(RAG)技术体系
  • 【Leetcode hot 100】739.每日温度
  • JAVA国际版任务悬赏发布接单系统源码支持IOS+Android+H5
  • 网站做选择题怎么快速选择艺术品拍卖网站源码php
  • 大模型微调完整步骤( LLama-Factory)
  • Rust + PostgreSQL:deadpool 和 diesel 数据库连接池实战(四)
  • 关于门户网站建设讲话关键词挖掘查询工具爱站网