【软考中级 - 软件设计师 - 基础知识】数据结构之栈与队列
栈和队列是 “受限的线性表”—— 它们在 linear 表的基础上增加了 “操作规则”,是算法设计和实际开发中最常用的结构(比如函数调用用栈、消息排队用队列)。考试中二者常结合 “操作顺序判断”“存储实现缺陷”“场景匹配” 出题,核心是掌握 “规则差异 + 关键操作”,下文分模块拆解。
一、先分清:栈与队列的核心规则(必记)
栈和队列的本质区别是 “元素进出的顺序规则”,用生活例子就能轻松记住:
数据结构 | 核心规则 | 生活例子 | 关键操作(考试重点) |
栈 | 先进后出(LIFO) | 叠盘子:先放的盘子在下面,后拿 | 入栈(push)、出栈(pop) |
队列 | 先进先出(FIFO) | 排队买票:先到的人先买 | 入队(enqueue)、出队(dequeue) |
直观对比:
- 栈:比如浏览器 “后退” 功能 —— 先打开的页面(A→B→C),后退时先退 C,再退 B,最后退 A(符合先进后出);
- 队列:比如打印机 “排队打印”—— 先提交的文档(1→2→3),先打印 1,再打印 2,最后打印 3(符合先进先出)。
二、核心考点 1:栈的存储与核心操作
栈的存储分 “顺序存储(数组)” 和 “链式存储(链表)”,考试重点考顺序存储(占比 80%),需掌握 “栈满 / 栈空判断” 和 “push/pop 操作步骤”。
1. 顺序存储(数组实现)
用数组存栈元素,额外用一个 “栈顶指针(top)” 记录栈顶位置(top 初始为 - 1,代表栈空):
- 栈空条件:top == -1;
- 栈满条件:top == 数组长度 - 1(比如数组长度为 5,top=4 时栈满)。
(1)入栈(push):往栈顶加元素
步骤(数组长度 5,初始栈空 top=-1,入栈元素 1、2、3):
Step1:判断是否栈满(若 top==4,报错 “栈满”);
Step2:top+1(top 从 - 1→0);
Step3:把元素放到数组 [top] 位置(数组 [0]=1);
重复 Step1-Step3,依次入栈 2、3:
- 入栈 2 后:top=1,数组 [1]=2;
- 入栈 3 后:top=2,数组 [2]=3;
最终栈内元素:[1,2,3](栈顶是 3)。
(2)出栈(pop):从栈顶拿元素
步骤(接上面的栈,出栈 2 次):
Step1:判断是否栈空(若 top==-1,报错 “栈空”);
Step2:取出数组 [top] 的元素(第一次取数组 [2]=3);
Step3:top-1(top 从 2→1);
重复 Step1-Step3,第二次出栈:
- 取出数组 [1]=2,top 从 1→0;
最终栈内剩余元素:[1](栈顶是 1)。
易错提醒:
- 顺序栈 “栈满后不能再入栈”,若要继续存元素,需 “扩容”(考试一般不考扩容细节,知道要扩容即可);
- 出栈只能拿栈顶元素,不能直接拿中间元素(比如上面的栈,不能直接取出 1,必须先出栈 3、2)。
2. 链式存储(链表实现)
用链表存栈元素,栈顶就是链表的 “头节点”(不用遍历,直接操作头节点,效率高):
- 栈空条件:头节点为 null;
- 入栈:新节点作为新头节点(新节点.next = 原头节点);
- 出栈:删除头节点,新头节点 = 原头节点.next;
例子:入栈 1→2→3,链表结构为:3(头节点)→2→1→null;
出栈 1 次:删除头节点 3,新头节点 = 2,链表变为 2→1→null。
适用场景:
顺序栈适合 “元素个数固定” 的场景(比如函数调用栈),链式栈适合 “元素个数不确定” 的场景(比如动态添加的任务栈)。
3. 栈的典型应用场景(真题高频)
应用场景 | 原理(先进后出) | 例子 |
表达式求值 | 用栈存运算符,优先级高的先计算 | 计算 “3+42”:先算 42,再算 3+8 |
函数调用栈 | 调用函数时入栈,函数返回时出栈 | 主函数调用 A,A 调用 B→B 返回后 A 再返回主函数 |
浏览器后退 / 编辑器撤销 | 每打开新页面 / 输入内容时入栈,后退 / 撤销时出栈 | 浏览器打开 A→B→C,后退先退 C |
三、核心考点 2:队列的存储与核心操作
队列的存储也分 “顺序存储” 和 “链式存储”,但顺序存储有 “假溢出” 问题,需用 “循环队列” 解决(考试重点考循环队列)。
1. 顺序存储的问题:假溢出
普通顺序队列用数组 +“队头指针(front)”“队尾指针(rear)” 实现:
- 队空条件:front == rear(初始都为 0);
- 入队:rear+1,数组 [rear] 存元素;
- 出队:front+1,取出数组 [front] 元素;
假溢出问题:比如数组长度 5,入队 1、2、3、4(rear=4),出队 1、2(front=2),此时数组还有 [3,4],但 rear=4 已到数组末尾,无法再入队 5(实际数组 [0,1] 是空的)—— 这就是 “假溢出”(明明有空间,却提示队满)。
2. 循环队列:解决假溢出(考试重点)
把数组 “首尾相连”,形成循环(rear 到数组末尾后,再从 0 开始),用 “取余(%)” 实现循环逻辑:
- 数组长度记为 maxSize;
- 队空条件:front == rear;
- 队满条件:(rear + 1) % maxSize == front(故意空一个位置,避免和队空混淆);
- 元素个数:(rear - front + maxSize) % maxSize(加 maxSize 是为了避免 rear<front 时出现负数)。
(1)入队(enqueue):循环队列存元素
步骤(maxSize=5,初始 front=0,rear=0,入队 1、2、3):
Step1:判断是否队满((rear+1)%5 == front?初始 rear=0,(0+1)%5=1≠0,不队满);
Step2:rear = (rear + 1) % 5(rear 从 0→1);
Step3:数组 [rear] = 元素(数组 [1]=1);
重复 Step1-Step3,入队 2、3:
- 入队 2 后:rear=(1+1)%5=2,数组 [2]=2;
- 入队 3 后:rear=(2+1)%5=3,数组 [3]=3;
此时队列元素:[,1,2,3,](front=0,rear=3)。
(2)出队(dequeue):循环队列取元素
步骤(接上面的队列,出队 2 次):
Step1:判断是否队空(front==rear?0≠3,不队空);
Step2:front = (front + 1) % 5(front 从 0→1);
Step3:取出数组 [front] 的元素(第一次取数组 [1]=1);
重复 Step1-Step3,第二次出队:
- front=(1+1)%5=2,取出数组 [2]=2;
此时队列元素:[, ,3,](front=2,rear=3),元素个数 =(3-2+5)%5=1。
关键计算(真题常考):
若循环队列 maxSize=6,front=4,rear=1,求元素个数:
元素个数 =(1-4+6)%6=3(队列里有 3 个元素)。
3. 链式存储(链表实现)
用链表存队列元素,队头是链表 “头节点”(出队快),队尾是链表 “尾节点”(入队快):
- 队空条件:头节点 ==null;
- 入队:新节点接在尾节点后,更新尾节点;
- 出队:删除头节点,更新头节点;
例子:入队 1→2→3,链表结构:头节点 1→2→尾节点 3;
出队 1 次:删除头节点 1,新头节点 = 2,链表变为 2→3(尾节点不变)。
适用场景:
循环队列适合 “元素个数固定” 的场景(比如固定大小的任务队列),链式队列适合 “元素个数不确定” 的场景(比如消息队列)。
4. 队列的典型应用场景(真题高频)
应用场景 | 原理(先进先出) | 例子 |
消息队列 | 先发送的消息先处理 | 用户发送的聊天消息,按顺序显示 |
任务调度 | 先提交的任务先执行 | 服务器处理用户请求,按请求顺序执行 |
缓冲区(如打印机) | 先提交的打印任务先打印 | 多用户提交打印,队列排序 |
四、高频考点对比:栈与队列的核心差异
考试常考 “操作顺序判断”,比如 “元素入栈 / 入队顺序为 1,2,3,可能的出栈 / 出队顺序是什么”,核心是记住规则差异:
对比维度 | 栈(先进后出) | 队列(先进先出) |
操作顺序判断 | 出栈顺序不唯一(比如入栈 1,2,3,出栈可 3,2,1 或 2,3,1) | 出队顺序唯一(入队 1,2,3,出队只能 1,2,3) |
存储关键问题 | 顺序栈:栈满扩容 | 顺序队列:假溢出(需循环队列解决) |
指针作用 | 仅栈顶指针(top) | 队头(front)+ 队尾(rear)指针 |
真题示例:
判断 “入栈顺序为 1,2,3,4,出栈顺序为 3,4,2,1” 是否合法?
分析:1 入→2 入→3 入→3 出→4 入→4 出→2 出→1 出,符合先进后出,合法。
五、备考小贴士(3 步搞定)
- 记规则:栈 “先进后出”、队列 “先进先出”,用生活例子(叠盘子、排队)辅助记忆;
- 练操作:重点练循环队列的 “队满 / 队空判断” 和 “元素个数计算”(比如 maxSize=5,front=3,rear=1,元素个数 =(1-3+5)%5=3);
- 场景对应:看到 “后退 / 撤销 / 函数调用” 选栈,看到 “消息 / 任务 / 排队” 选队列,不用想复杂。
下一篇【基础知识】专栏将讲解 “树结构基础”(二叉树的定义、遍历),树是 “非线性结构” 的入门,和栈、队列关联紧密(比如二叉树遍历用栈或队列实现),建议提前回顾栈和队列的操作逻辑。