第3章栈和队列

第3章栈和队列

3.1栈的定义和特点

1.章节定位与学习框架

(1)数据结构板块划分

数据结构模块分为上下两篇,整体学习顺序如下:

  • 上篇(线性结构):第 2 章 线性表 → 第 3 章 栈和队列(本章)
  • 下篇(非线性结构):第 5 章 树和二叉树 → 第 6 章 图
  • 核心逻辑:从 "简单线性结构" 到 "常用线性结构",再到 "复杂非线性结构",循序渐进。

(2)本章核心内容

第 3 章包含两大核心线性结构:队列,二者被放在同一章学习的原因:

  1. 均为 "最常用的线性结构",实际编程(如 C/C++)中应用广泛;
  2. 学习方法高度相似(均遵循 "定义 - 描述 - 操作 - 存储" 四要点);
  3. 逻辑特点互补(栈:先进后出;队列:先进先出),对比学习易理解。

2.数据结构通用学习方法(关键!)

1.四要点学习框架

无论是线性表、栈、队列,还是后续的树、图,均围绕以下 4 个核心要点展开,是贯穿整个数据结构学习的 "通用逻辑":

序号 学习要点 核心内容说明
1 定义(含特点) 明确结构的本质的限定条件、核心特性
2 描述 用语言 / 图示清晰表达结构的逻辑形态
3 操作 结构支持的核心功能(如插入、删除)
4 存储 结构在内存中的实现方式(如顺序存储、链式存储)

2.本章适用说明

栈和队列的学习完全遵循上述框架,本章先学 "栈" 的前 2 个要点(定义、描述),后续课时会展开 "操作" 和 "存储";队列的学习逻辑与栈一致,后续同步推进。

3.第一部分:栈的基础认知

(1)栈的定义(本质 + 限定条件)

(a) 核心定义

栈是限定只在线性表的一端进行插入和删除操作的线性表

  • 关键 1:栈属于 "线性表",具备线性表 "元素有序、一对一逻辑关系" 的本质;
  • 关键 2:与普通线性表的核心区别 ------操作位置限定:仅允许在 "一端" 进行插入和删除,普通线性表可在任意位置操作。
(b) 易错辨析
  • 正确表述:栈是一种线性表(满足线性表本质);
  • 错误表述:线性表就是栈(普通线性表无操作位置限定,范围更广)。

(2)栈的核心术语(必记)

术语 英文 功能 / 特点
栈顶 Top 允许进行插入、删除操作的一端
栈底 Bottom 不允许任何插入、删除操作的一端
入栈(压栈) Push 在栈顶插入元素的操作
出栈(弹栈) Pop 在栈顶删除元素的操作

(3)栈的核心特点:后进先出(LIFO)

(a)特点本质

由 "仅栈顶操作" 的限定条件决定:后进入栈的元素会 "压在栈顶",先进入的元素会 "被压在栈底",删除时只能先删栈顶元素,导致 "后入的先出,先入的后出"。

(b)实例理解(视频图示逻辑)

假设元素按 "A₀ → A₁ → A₂ → ... → Aₙ₋₁" 的顺序入栈(依次从栈顶压入):

  • 入栈顺序:A₀(最先入,在栈底)→ A₁ → ... → Aₙ₋₁(最后入,在栈顶);
  • 出栈顺序:Aₙ₋₁(最先出,栈顶元素)→ Aₙ₋₂ → ... → A₀(最后出,栈底元素);
  • 结论:入栈与出栈顺序完全相反,即 "后进先出(LIFO,Last In First Out)",也可表述为 "先进后出"。

(4)栈的优缺点与实际应用

(a)核心优缺点
优点 缺点
程序可读性极强(逻辑清晰,符合人类理解习惯) 效率较低(频繁进行入栈、出栈操作,内存开销较大)
(b)实际应用场景

视频明确提到:在 C/C++ 前导课程中 "大量使用",核心作用是简化程序逻辑,让代码更易理解(如函数调用栈、表达式求值等场景)。

4.本章后续学习预告

  1. 栈的深入学习:栈的核心操作(入栈、出栈、判空、取栈顶元素等)、栈的存储实现(顺序存储、链式存储);
  2. 队列的学习:按 "定义 - 描述 - 操作 - 存储" 四要点,对比栈的 "后进先出" 学习队列的 "先进先出";
  3. 习题巩固:针对栈和队列的核心知识点,通过习题强化理解(对应本章课时 14)。

3.2栈的表示和操作的实现

1.栈的存储结构

栈的存储理论上包含 4 种(顺序、链式、索引、散列),但本章重点学习顺序存储(顺序栈)链式存储(链式栈),其余两种暂不深入。

(1)顺序栈

(a)定义
  • 限定在表尾进行插入(进栈)和删除(退栈)的栈,仅表尾可操作,其他端不可用。
  • 核心指针:
    • 栈底指针(bottom):始终指向栈底,位置固定。
    • 栈顶指针(top) :指向栈顶元素的上方存储单元(空单元),随元素进出移动。
(b)核心操作(结合示意图逻辑)
操作 步骤 示例(依次插入 A、B、C、D、E、K,再依次删除)
进栈(插入) 1. 将元素存入当前top指向的存储单元; 2. top向上移动(top++)。 插入 A:A 存入top位置→top上移; 插入 K 后:K 存入对应单元→top移至 K 上方。
退栈(删除) 1. top向下移动(top--)(指向栈顶元素); 2. 弹出当前top指向的元素。 删 K:top--(指向 K)→弹出 K; 删 A 后:top回到空栈初始位置→无元素可删。
(c)溢出现象(操作禁忌)
  • 上溢(overflow):栈满时继续进站。

    例:栈容量固定,插入 K 后再插 L,无空单元可用→无法进站。

  • 下溢(underflow):空栈时继续退站。

    例:弹出所有元素(A~K)后,再执行退站→无元素可弹。

    ✅ 考试提示:若记不清 "上溢 / 下溢",可统称 "溢出";但编程中需明确区分,避免逻辑错误。

(2)链式栈

(a)定义
  • 用链表存储栈元素,节点包含数据域(data,存元素)指针域(next,存后继节点地址)
  • 核心特点:
    • 通常不带头节点 ,插入 / 删除仅在表头(栈顶) 进行;
    • 栈顶指针(top)= 链表头指针,始终指向栈顶节点。
(a)关键特性
特性 说明
空栈状态 top指向null(无节点)。
满栈状态 通常不讨论(链表可动态申请空间,仅当内存无法申请时视为 "满")。
与顺序栈对比 优势不突出: 1. 栈仅在栈顶操作,无需频繁移动元素(链表解决 "移动效率低" 的优势无用); 2. 指针域额外占用空间。✅ 结论:无特殊说明时,栈优先用顺序存储。

2.栈操作的关键问题(高频考点)

(1)top指针的移动顺序(核心逻辑)

操作 顺序(初始化时top指向顺序表第一个位置) 原因
进栈 先存元素 → 再移toptop++ 确保top始终指向栈顶元素的上方空单元。
退栈 先移toptop--) → 再弹元素 初始top指向空单元,需先下移指向栈顶元素,才能弹出。

(2)栈的生活类比(理解 "后进先出")

  • 示例:军训打枪的子弹装填与发射

    最后压入枪膛的子弹 → 最先发射(后进先出);

    最先压入枪膛的子弹 → 最后发射(先进后出)。

    ✅ 栈的核心特性:

    后进先出(LIFO),仅栈顶可操作。

3.栈的基本操作(4 个核心操作)

操作名称 定义 关键细节
1. 栈的初始化 将栈置为空栈。 top指向初始位置(顺序表首地址),bottom指向栈底,栈内无元素。
2. 读取栈顶元素 仅获取栈顶元素的值,不修改栈内数据。 不移动top指针,不改变栈结构(内存 "读操作" 特性:仅读取,不改写数据)。
3. 进栈(插入) 将元素插入栈顶。 步骤:存元素→top++(顺序栈);新节点接在表头→top指向新节点(链式栈)。
4. 退栈(删除) 将栈顶元素弹出并从栈中移除。 步骤:top--→弹元素(顺序栈);top指向后继节点→释放原栈顶节点(链式栈)。
✅ 后续任务:巩固提升篇需通过编程实现这 4 个操作。

4.栈的典型应用(理论 + 实例)

(1)数值转换(核心方法:除 D 取余,余数倒排)

(a)原理

十进制数N转换为基数为D(如 2→二进制、8→八进制、16→十六进制)的数时,需通过 "除 D 取余" 得到余数,再将余数 "倒排" 得到结果 ------栈的 "后进先出" 特性完美适配 "倒排" 需求

(b)实例
转换需求 步骤(除 D 取余,余数入栈,最后出栈倒排) 结果
十进制 75→二进制(D=2) 75÷2=37 余 1(入栈)→37÷2=18 余 1(入栈)→18÷2=9 余 0(入栈)→9÷2=4 余 1(入栈)→4÷2=2 余 0(入栈)→2÷2=1 余 0(入栈)→1÷2=0 余 1(入栈)→出栈顺序:1、0、0、1、0、1、1 1001011
十进制 1348→八进制(D=8) 1348÷8=168 余 4(入栈)→168÷8=21 余 0(入栈)→21÷8=2 余 5(入栈)→2÷8=0 余 2(入栈)→出栈顺序:2、5、0、4 2504

(2)表达式求值(中缀表达式用双栈,后缀表达式用单栈)

①核心概念
  • 中缀表达式 :运算符在操作数中间(如3*(7-2))→需用双栈(数据栈 + 运算符栈)求值;
  • 后缀表达式(逆波兰式) :运算符在操作数后面(如3 7 2 - *)→需用单栈求值(需先将中缀转后缀)。
②中缀表达式求值(重点:双栈配合 + 优先级规则)
(a)运算符优先级规则(必记)
  1. 表达式前后补 "#",标记开始和结束,"#" 优先级最低
  2. 乘除(×、÷)优先级 > 加减(+、-);
  3. 左括号 "(":优先级> 括号外运算符,< 括号内运算符;
  4. 右括号 ")":优先级 < 括号内运算符,遇 "(" 时,两者同时出栈(数据栈不操作);
  5. 两 "#" 相遇→表达式计算结束,所有栈元素弹出。
(b)实例:计算3*(7-2)(补全为#3*(7-2)#
扫描字符 数据栈(存操作数) 运算符栈(存运算符) 操作逻辑
# [] [#] 运算符栈空,压入 "#"。
3 [3] [#] 数据直接压入数据栈。
* [3] [#, *] "*" 优先级 > 栈顶 "#",压入运算符栈。
( [3] [#, *, (] "(" 优先级> 栈顶 "*",压入运算符栈。
7 [3, 7] [#, *, (] 数据直接压入数据栈。
- [3, 7] [#, *, (, -] "-" 优先级 > 栈顶 "(",压入运算符栈。
2 [3, 7, 2] [#, *, (, -] 数据直接压入数据栈。
) [3, 5] [#, *] 1. ")" 优先级 < 栈顶 "-",弹出 "-";2. 数据栈弹 2(第二个操作数)、弹 7(第一个操作数),算 7-2=5,压回数据栈;3. 运算符栈顶为 "(",与 ")" 配对,同时弹出。
# [15] [] 1. "#" 优先级 < 栈顶 "",弹出 "";2. 数据栈弹 5(第二个操作数)、弹 3(第一个操作数),算 3×5=15,压回数据栈;3. 两 "#" 相遇,计算结束,弹出所有栈元素。
✅ 最终结果:数据栈剩余 15,即3*(7-2)=15

1.初始化双栈:数据栈为空,运算符栈先压入左侧 "#";

2.从左到右扫描表达式:

遇到 "3"(数据):直接压入数据栈;

3.遇到 "*"(运算符):优先级高于栈顶 "#",压入运算符栈;

4.遇到 "("(左括号):优先级高于栈顶 "*",压入运算符栈;

5.遇到 "7"(数据):压入数据栈;

6.遇到 "-"(运算符):优先级高于栈顶 "(",压入运算符栈;

7.遇到 "2"(数据):压入数据栈;

8.遇到 ")"(右括号):优先级低于栈顶 "-",需先弹出 "-";此时从数据栈弹出两个元素(先弹 2,后弹 7),计算 7-2=5,将 5 压回数据栈;接着运算符栈顶为 "(",与 ")" 配对,两者同时弹出(数据栈不操作);

9.遇到 "#"(右侧):优先级低于栈顶 "",弹出 "";从数据栈弹出两个元素(先弹 5,后弹 3),计算 3*5=15,将 15 压回数据栈;最后两个 "#" 相遇,表达式计算结束,弹出所有栈元素,数据栈中剩余的 15 就是结果。

5.队列引入

本章为 "栈和队列",当前已完成栈的核心内容(存储、操作、应用),下一板块将学习第三章第二个重点 ------队列(定义、存储结构、操作及应用)。

6.小结

  1. 栈的核心:仅栈顶可操作,后进先出(LIFO);
  2. 存储优先:无特殊说明时,栈用顺序存储(链式栈优势不突出);
  3. 高频考点:top指针移动顺序、溢出现象、中缀表达式双栈求值、数值转换的 "除 D 取余倒排";
  4. 应用关键:栈是解决 "需倒序处理数据" 问题的核心数据结构。

3.3队列的定义和特点

1.队列的定义(★核心概念)

  • 官方定义 :限制在表的一端进行删除操作表的另一端进行插入操作的线性表。

  • 与栈的定义对比(视频重点强调)

数据结构 操作限制 核心差异点
仅允许在同一端进行插入和删除 单端 "一站式" 操作(插 + 删)
队列 两端分工:一端只删、一端只插 双端 "分工式" 操作(插删分离)
  • 本质:通过 "操作位置限制" 形成有序的线性表,保证数据处理的规律性。

2.队列的核心特点(★高频考点)

  • 核心规律先进先出(FIFO,First In First Out)

  • 视频生活化举例:

    • 排队买票:先到者站在队前,先买票离开(符合 "先入先出");
    • 上公交:先排队的人先上车,后排队的人依次在后(若用栈的 "后进先出",会出现 "后到先上车",与现实逻辑相悖);
    • 关键提醒:"排队" 本质就是队列的现实体现,视频调侃 "排队不叫排站",目的是强化 "队列≠栈" 的认知。

3.队列的关键术语(★基础必备)

为明确两端的操作分工,视频定义两个核心术语,需结合操作记忆:

术语 英文 对应操作 现实场景类比
队头 front 仅负责 "出队"(删除) 排队时 "先买票离开的一端"
队尾 rear 仅负责 "入队"(插入) 排队时 "新加入者排队的一端"
  • 操作规则强化 :入队只能在队尾,出队只能在队头(不可混淆,否则破坏 FIFO 规律),可简单记为 "尾进头出"。

4.队列的 7 个基本操作(★考研基础操作)

视频明确队列需掌握 7 个核心操作,每个操作需明确 "目的 + 注意事项",避免错误使用:

函数名 操作名称 操作目的 注意事项 / 补充说明
Status InitQueue(SqQueue &Q) 初始化 创建一个空队列,初始化队头、队尾指针等结构 后续所有操作的前提,需确保初始状态合法
int QueueLength(SqQueue Q) 求队列长度 统计队列中当前元素的个数 需遍历或通过指针计算(后续存储实现会讲具体方法)
Status EnQueue(SqQueue &Q, QElemType e) 入队(插入) 在队尾添加新元素 需先判断队列是否 "满",避免 "满队列入队"(溢出)
Status DeQueue(SqQueue &Q, QElemType &e) 出队(删除) 在队头移除并返回元素 需先判断队列是否 "空",避免 "空队列出队"(错误)
Status QueueEmpty(SqQueue Q) 判断队列空 检查队列是否无元素(队头 = 队尾等标识) 出队、取队头元素前的必要判断
Status QueueFull(SqQueue Q) 判断队列满 检查队列是否达到存储上限 入队前的必要判断,防止元素溢出
Status GetHead(SqQueue Q, QElemType &e) 取队头元素 读取队头元素的值(不删除元素 与 "出队" 的核心区别:仅读不删,需先判断队列非空

5.视频重点强调的易错点 & 记忆技巧

  1. 易错点:栈与队列的操作端混淆
  • 栈:"一端管插又管删"(如杯子,只能从开口端放东西、拿东西);

  • 队列:"一端只插、一端只删"(如水管,一端进水、一端出水,方向固定)。

  1. 记忆技巧
  • 用 "生活场景锚定":想到 "排队" 就对应队列,想到 "叠盘子"(后放的先拿)对应栈;

  • 术语口诀:"前(队头)出后(队尾)进,先进先出"。

3.4队列的表示和操作的实现

1.队列的存储结构

(1)存储分类(4 种,核心记 2 种)​

  • 顺序存储、链式存储、索引存储、散列存储

  • 实际应用核心:仅需掌握 "顺序存储" 和 "链式存储",以顺序存储为主

(2)顺序存储(重点)

  • 存储方式:用地址连续的存储单元,按 "队头→队尾" 依次存放

  • 关键指针(视频反复强调):

    • Q.front:指向队头(元素存放位置)

    • Q.rear:指向队尾的下一个位置(非队尾元素本身,易错点)

  • 初始状态:队空时 front == rear == 0(指针重合)

  • 操作规则(与栈的区别):

    • 进队:仅动rear,rear++(先存元素,再移指针)

    • 出队:仅动front,front++(先取元素,再移指针)

    • 栈动top(进 + 1 / 出 - 1),队列分动两个指针(进动 rear / 出动 front)

(3)链式存储(了解即可,视频不推荐用)

  • 结构:同样用front(队头)、rear(队尾)指针,仅在两端插入 / 删除

  • 缺点(视频明确否定):

    • 需额外空间存节点地址,浪费资源

    • 无法体现链表 "任意位置插入删除" 的优势(队列仅两端操作)

  • 结论:实际优先用 "顺序存储",链式存储无需深入掌握

2.顺序队列的致命问题:假满

(1)假满现象(视频举例说明)

  • 场景:当rear指向存储空间末尾(如申请 5 个空间,rear=5),即使front前有空闲(如 front=2,前 2 个位置空),也无法进队

  • 本质:"看似满,实则有空闲",空间浪费

(2)问题原因

  • front和rear仅单向移动,无法回头利用已出队元素的空闲空间

  • 例:5 个空间存 10、20、30、40、50(rear=5),出队 10、20(front=2),此时想进 60 却因 rear=5 无法进,形成假满

3.循环队列(核心考点)

(1)定义与核心价值(视频强调 "考试默认队列 = 循环队列")

  • 逻辑设计:将顺序队列的存储空间 "逻辑上连成环",让front和rear可循环移动

  • 解决问题:彻底消除 "假满",节省空间

  • 实现关键:取余运算((指针+1) % max),让指针到末尾后跳回起始位置(如 max=10,rear=9 时,(9+1)%10=0,跳回 0 位置)

(2)核心前提:牺牲 1 个空间(区分空 / 满的关键)

  • 问题:循环队列中,"空" 和 "满" 若都用front==rear,计算机无法区分

  • 解决方案(严蔚敏教材方案,视频指定考点):牺牲 1 个存储空间,用 "满时留 1 个空位置" 区分

  • 例:申请 10 个空间(max=10),最多存 9 个元素(max-1)

(3)循环队列六大关键操作(视频逐句推导,考试必背)

操作类型 操作逻辑 / 公式 视频实例辅助(max=10)
1. 最大存储数 最多存 max - 1 个元素(因牺牲 1 个空间) max=10→最多存 9 个,max=15→最多存 14 个
2. 队空判断 front == rear(指针重合,无元素) front=4、rear=4→队空
3. 队满判断 (rear + 1) % max == front(rear 加 1 后追上 front,留 1 个空) front=2、rear=1→(1+1)%10=2==front→满
4. 进队操作 1. 先判断是否满; 2. 不满则将元素存入 rear 位置; 3. 更新rear = (rear+1) % max rear=7→存元素→rear=(7+1)%10=8
5. 出队操作 1. 先判断是否空; 2. 非空则取 front 位置元素; 3. 更新front = (front+1) % max front=4→取元素→front=(4+1)%10=5
6. 实际元素个数 (rear - front + max) % max(覆盖两种场景,视频推导验证) 场景 1:front=4、rear=7→(7-4+10)%10=3;场景 2:front=6、rear=2→(2-6+10)%10=6

4.视频核心考点 & 易错提醒(老师反复强调)

  1. 术语必记:front(队头指针)、rear(队尾下一个位置指针)、取余运算(循环核心),勿混淆
  2. 考试默认:无论题目是否说明,"队列" 均指 "循环队列",直接用六大操作解题
  3. 公式易错点:实际元素个数公式必须加max再取余(避免 rear<front 时出现负数,如 front=6、rear=2,(2-6) 为负,加 10 后再取余得 6)
  4. 操作顺序:进队先存元素再移 rear,出队先取元素再移 front(顺序反了会出错)

参考资料:教材《数据结构 C 语言 第 3 版》 数据结构考研指导(基础篇) 、数据结构考研指导(基础篇) 视频课程|赵海英