考点频率 :★★★★★(数据结构必考,选择题常考栈的特性与应用场景)
难度 :⭐⭐
建议:重点理解栈的"后进先出(LIFO)"特性,掌握顺序栈与链式栈的区别,以及栈在递归、表达式求值等场景中的应用
1️⃣ 什么是栈?
栈(Stack) 是一种操作受限 的线性表。它的核心特征是:只允许在表的一端进行插入和删除操作 。这一端被称为栈顶(Top) ,另一端被称为栈底(Bottom)。
核心特性 :后进先出(LIFO,Last In First Out)------最后一个进去的,最先出来。
打个比方 :栈就像一叠盘子。你只能从最上面拿走一个盘子(出栈),也只能把干净的盘子放在最上面(入栈)。你不能直接从中间抽走一个盘子,也不能把盘子塞到中间------这就是"操作受限"的含义。
2️⃣ 栈的基本操作
| 操作 | 含义 | 时间复杂度 |
|---|---|---|
| 入栈(Push) | 将元素放入栈顶 | O(1)O(1)O(1) |
| 出栈(Pop) | 移除栈顶元素并返回 | O(1)O(1)O(1) |
| 取栈顶(Peek / Top) | 查看栈顶元素但不移除 | O(1)O(1)O(1) |
| 判空(IsEmpty) | 检查栈是否为空 | O(1)O(1)O(1) |
| 判满(IsFull) | 检查栈是否已满(顺序栈) | O(1)O(1)O(1) |
核心规律 :所有操作都只发生在栈顶,与栈底无关。
3️⃣ 栈的存储方式
和线性表一样,栈也有两种存储方式:
| 存储方式 | 说明 |
|---|---|
| 顺序栈(Array-based Stack) | 用数组 实现的栈,栈顶位置用下标 top 记录 |
| 链式栈(Linked Stack) | 用单链表实现的栈,栈顶是链表的头节点 |
4️⃣ 顺序栈(Sequential Stack)
4.1 核心结构
顺序栈用数组 作为底层存储,用一个整型变量 top(栈顶指针)记录栈顶元素的当前位置。
c
#define MAXSIZE 100
typedef struct {
int data[MAXSIZE]; // 数组存储元素
int top; // 栈顶指针(指向栈顶元素位置)
} SeqStack;
4.2 栈顶指针的含义
| 情况 | top 值 |
含义 |
|---|---|---|
| 栈空 | -1 |
没有任何元素 |
| 有 1 个元素 | 0 |
第 0 个位置有数据 |
| 有 n 个元素 | n-1 |
最后一个数据在 n-1 位置 |
注意 :栈顶指针的初始值在不同教材中有两种约定:
top = -1(指向栈顶元素)和top = 0(指向下一个空闲位置)。软考中通常使用top = -1表示栈空,用++top入栈、top--出栈。
4.3 基本操作
入栈(Push):
- 检查栈是否已满
top++(栈顶指针上移)data[top] = 新元素
出栈(Pop):
- 检查栈是否为空
- 取出
data[top] top--(栈顶指针下移)
4.4 顺序栈的优缺点
| 优点 | 缺点 |
|---|---|
| 实现简单,代码直观 | 容量固定,可能浪费内存或溢出 |
| 所有操作 O(1)O(1)O(1) | 需要预先分配连续空间 |
| 无需额外指针存储 | 扩容成本高(需复制整个数组) |
5️⃣ 链式栈(Linked Stack)
5.1 核心结构
链式栈用单链表 作为底层存储,链表的头节点就是栈顶,链表的尾节点就是栈底。
c
// 链式栈的节点结构
typedef struct StackNode {
int data; // 数据域
struct StackNode* next; // 指针域
} StackNode;
// 链式栈结构(只需记录栈顶指针)
typedef struct {
StackNode* top; // 栈顶指针(指向头节点)
int size; // 栈中元素个数(可选)
} LinkStack;
5.2 基本操作
入栈(Push):
- 创建新节点,填入数据
- 新节点的
next指向原来的栈顶 - 更新栈顶指针指向新节点
c
newNode->next = top;
top = newNode;
出栈(Pop):
- 检查栈是否为空
- 保存栈顶节点的数据
temp = top,top = top->next- 释放
temp节点
5.3 链式栈的优缺点
| 优点 | 缺点 |
|---|---|
| 容量无限(受内存限制) | 每个节点需要额外的指针空间 |
| 动态分配,按需增长 | 存储密度低 |
| 不需要预先知道最大容量 | 节点分散,CPU缓存命中率低 |
6️⃣ 顺序栈 vs 链式栈(对比表)
| 对比项 | 顺序栈 | 链式栈 |
|---|---|---|
| 底层结构 | 数组 | 单链表 |
| 容量 | 固定(静态数组)或可扩容(动态数组) | 动态增长 |
| 存储密度 | 高(无指针开销) | 低(有指针开销) |
| 操作时间复杂度 | 全部 O(1)O(1)O(1) | 全部 O(1)O(1)O(1) |
| 是否需要判满 | 是(静态数组时) | 否 |
| 内存分配 | 预先分配连续空间 | 按需分配 |
| 适用场景 | 元素个数可预知 | 元素个数不可预知 |
7️⃣ 栈的典型应用(软考常考)
| 应用场景 | 说明 |
|---|---|
| 函数调用与递归 | 系统用栈保存函数的返回地址和局部变量 |
| 表达式求值 | 中缀表达式转后缀表达式、后缀表达式求值 |
| 括号匹配 | 检查程序中的 (){}[] 是否配对 |
| 撤销操作(Undo) | 编辑器中的撤销功能用栈记录操作历史 |
| 浏览器前进后退 | 用两个栈实现页面历史管理 |
8️⃣ 经典例题
例题1 :一个栈的入栈序列为 1, 2, 3, 4, 5,以下哪个不可能是合法的出栈序列?
A. 1, 2, 3, 4, 5
B. 3, 2, 1, 5, 4
C. 4, 5, 3, 2, 1
D. 4, 3, 5, 1, 2
解析 :D 中 1 在 2 之前出栈,但 1 是最先入栈的,如果 1 要出栈,2, 3, 4, 5 必须都已经入栈且 2 在 1 之上。D 中 4, 3, 5, 1, 2 顺序不可能出现,因为 2 不可能在 1 之后出栈(2 在 1 上方,必须先出栈)。选 D。
例题2:以下哪种数据结构最适合实现函数调用中的"返回地址"存储?
A. 队列
B. 栈
C. 数组
D. 链表
解析 :函数调用的返回顺序是后调用的先返回 ,符合栈的 LIFO 特性。选 B。
9️⃣ 记忆口诀
栈是受限线性表,后进先出是核心。
入栈出栈都在顶,顺序链式两种存。
顺序数组要判满,链式无限靠指针。
🔟 小测验(评论区对答案)
一个栈的入栈序列为
A, B, C, D,出栈序列不可能是( )。A.
A, B, C, DB.
D, C, B, AC.
B, A, D, CD.
D, A, B, C
🔔 本专栏日更,点击头像 → 专栏《软考中级高频考点》订阅,第一时间接收新内容
#软考中级 #软件设计师 #栈 #顺序栈 #链式栈 #数据结构 #软考备考