3.1 栈
3.1.1 栈的定义
**栈(Stack)**是一种只允许在一端进行插入和删除操作的线性表。
允许操作的一端称为栈顶,另一端称为栈底。
栈遵循:
后进先出(LIFO,Last In First Out)。
3.1.2 栈的基本操作
| 操作 | 含义 |
|---|---|
push |
入栈,将元素放入栈顶 |
pop |
出栈,移除并返回栈顶元素 |
peek |
获取栈顶元素但不删除 |
isEmpty |
判断栈是否为空 |
size |
获取栈中元素数量 |
3.1.3 栈的顺序存储
- 使用数组保存栈元素,并通过栈顶下标管理入栈、出栈操作。
java
class ArrayStack {
private final int[] data;
private int top = -1;
public ArrayStack(int capacity) {
data = new int[capacity];
}
public boolean isEmpty() {
return top == -1;
}
public boolean isFull() {
return top == data.length - 1;
}
public void push(int value) {
if (isFull()) {
throw new IllegalStateException("栈已满");
}
data[++top] = value;
}
public int pop() {
if (isEmpty()) {
throw new IllegalStateException("栈为空");
}
return data[top--];
}
public int peek() {
if (isEmpty()) {
throw new IllegalStateException("栈为空");
}
return data[top];
}
}
其中:
top = -1表示栈为空。- 入栈前先将
top加一。- 出栈时先读取栈顶元素,再将
top减一。
3.1.4 栈的链式存储
链栈使用链表结点保存元素,通常在链表头部进行入栈和出栈。
特点:
- 不需要预先固定数组容量。
- 入栈和出栈通常为 (O(1))。
- 需要额外存储指针。
- 仍可能受到可用内存限制。
3.1.5 栈的应用
- 函数调用与递归。
- 括号匹配。
java
- 表达式求值。
- 深度优先搜索。
- 撤销操作。
- 浏览器历史记录中的部分回退机制。
本节总结: 栈的核心是后进先出。入栈和出栈通常都可以在 (O(1)) 时间内完成,常用于需要逆序处理或保存执行状态的场景。
3.2 队列
3.2.1 队列的定义
**队列(Queue)**是一种只允许在一端插入、另一端删除的线性表。
- 队尾:插入元素的一端。
- 队头:删除元素的一端。
队列遵循:
先进先出(FIFO,First In First Out)。
3.2.2 队列的基本操作
| 操作 | 含义 |
|---|---|
offer / enqueue |
入队 |
poll / dequeue |
出队 |
peek |
查看队头元素 |
isEmpty |
判断队列是否为空 |
size |
获取队列元素数量 |
具体返回值和异常行为取决于所使用的队列实现。
3.2.3 队列的顺序存储
普通顺序队列可以使用数组保存元素,并维护队头、队尾位置。
若仅使用不断递增的数组下标,出队后前面的空间可能无法直接复用,容易造成假溢出。
3.2.4 循环队列
循环队列将数组的首尾逻辑连接,使队列能够循环使用数组空间。
若数组长度为capacity,常用的下标更新方法为:
java
rear = (rear + 1) % capacity;
front = (front + 1) % capacity;
取模操作可以让下标到达数组末尾后重新回到开头。
一种常见实现方式是预留一个空位置:
- 队列为空:
front == rear- 队列为满:
(rear + 1) % capacity == front
java
此时实际可存储的最大元素数为 `capacity - 1`。
3.2.5 队列的链式存储
链队列通过链表存储元素,通常设置队头指针和队尾指针。
- 入队:在队尾添加结点。
- 出队:删除队头结点。
- 空队列时,需要同时维护队头和队尾指针。
- 在实现正确的情况下,链队列的入队和出队通常都可以在 (O(1)) 时间内完成。
3.2.6 队列的应用
- 操作系统任务调度。
- 打印任务排队。
- 广度优先搜索。
- 消息队列。
- 缓冲区管理。
- 按到达顺序处理请求。
本节总结: 队列遵循先进先出。循环队列用于复用数组空间,链队列通过指针连接结点,两者都可以高效实现入队和出队。
3.3 栈与队列的比较
| 比较项目 | 栈 | 队列 |
|---|---|---|
| 访问原则 | 后进先出 | 先进先出 |
| 插入位置 | 栈顶 | 队尾 |
| 删除位置 | 栈顶 | 队头 |
| 主要操作 | 入栈、出栈 | 入队、出队 |
| 常见实现 | 数组、链表 | 循环数组、链表 |
| 常见应用 | 递归、表达式求值、括号匹配 | 任务调度、层序遍历、消息处理 |
本节总结: 栈和队列都属于受限线性表,区别在于插入和删除的位置不同,因此形成了不同的数据处理顺序。
3.4 双端队列
3.4.1 定义
双端队列(Deque) 允许在队列的两端进行插入和删除操作。
常见操作包括:
- 队头插入。
- 队尾插入。
- 队头删除。
- 队尾删除。
3.4.2 Java 中的双端队列
Java 中常用
Deque接口表示双端队列,ArrayDeque是常见实现类。
java
import java.util.ArrayDeque;
import java.util.Deque;
public class DequeDemo {
public static void main(String[] args) {
Deque<Integer> deque = new ArrayDeque<>();
deque.offerFirst(10);
deque.offerLast(20);
System.out.println(deque.peekFirst());
System.out.println(deque.peekLast());
deque.pollFirst();
deque.pollLast();
}
}
常用方法:
| 方法 | 含义 |
|---|---|
offerFirst(e) |
在队头插入 |
offerLast(e) |
在队尾插入 |
pollFirst() |
删除并返回队头元素 |
pollLast() |
删除并返回队尾元素 |
peekFirst() |
查看队头元素 |
peekLast() |
查看队尾元素 |
ArrayDeque 不允许存储 null 元素。
3.4.3 双端队列的应用
- 实现栈和队列。
- 滑动窗口问题。
- 双向调度。
- 需要在两端灵活操作的数据处理场景。
本节总结: 双端队列兼具两端插入和删除的能力。在 Java 中,Deque 是接口,ArrayDeque 是常用实现类。
3.5 栈在表达式求值中的应用
3.5.1 常见表达式形式
| 表达式类型 | 特点 | 示例 |
|---|---|---|
| 中缀表达式 | 运算符位于操作数之间 | A + B |
| 前缀表达式 | 运算符位于操作数之前 | + A B |
| 后缀表达式 | 运算符位于操作数之后 | A B + |
3.5.2 运算符优先级
常见运算符优先级从高到低:
- 括号中的表达式。
- 乘法、除法、取余。
- 加法、减法。
- 同一优先级下的运算通常按照运算符结合性处理。具体表达式还需要考虑括号、单目运算符等规则。
3.5.3 后缀表达式求值
-
后缀表达式可以借助栈计算。
-
基本步骤:
- 从左向右扫描表达式。
- 遇到操作数时,将操作数入栈。
- 遇到二元运算符时,先弹出右操作数,再弹出左操作数。
- 执行运算,并将结果重新入栈。
- 扫描结束后,栈顶元素为结果。
例如:
java
3 4 + 2 *
-计算过程:
3入栈。4入栈。- 遇到
+,计算3 + 4 = 7。7入栈。2入栈。- 遇到
*,计算7 * 2 = 14。
最终结果:
java
14
本节总结: 栈可以保存尚未完成运算的操作数和运算符。后缀表达式求值时,二元运算符需要先取右操作数,再取左操作数。
3.6 数组与特殊矩阵的存储
3.6.1 数组的基本概念
1.数组由相同类型的元素组成,元素通过下标访问。
2.在顺序存储的情况下,数组元素通常占据连续的存储空间。
3.二维数组可以看作由多个一维数组组成的逻辑结构,其实际存储顺序由具体语言和实现决定。
3.6.2 特殊矩阵
特殊矩阵是具有特定规律的矩阵,例如:
- 对称矩阵。
- 上三角矩阵。
- 下三角矩阵。
- 对角矩阵。
当矩阵中的大量元素具有固定值或可由其他元素推导时,可以只存储必要的数据,以节省空间。
3.6.3 对称矩阵
对称矩阵满足:
java
a{i,j}=a{j,i}
因此,主对角线两侧的元素可以相互对应,只存储其中一侧及主对角线元素即可恢复整个矩阵。
3.6.4 稀疏矩阵
稀疏矩阵是非零元素数量远少于矩阵元素总数的矩阵。
常见存储方式:
- 三元组表:保存行号、列号和值。
- 十字链表:通过链表组织非零元素及其行列关系。
- 压缩稀疏行或列存储:常用于科学计算和数值分析。
- 三元组的基本结构:
| 行号 | 列号 | 元素值 |
|---|---|---|
| (i) | (j) | (a_{ij}) |
- 仅保存非零元素及其位置,可以减少稀疏矩阵的存储开销。
本节总结: 数组支持按下标访问,特殊矩阵可以利用数学规律压缩存储,稀疏矩阵则通过只保存非零元素减少空间占用。
第三章重点知识汇总
| 知识点 | 核心内容 |
|---|---|
| 栈 | 后进先出,主要操作是入栈和出栈 |
| 队列 | 先进先出,主要操作是入队和出队 |
| 循环队列 | 利用取模操作循环使用数组空间 |
| 链队列 | 通过链表连接元素,维护队头和队尾 |
| 双端队列 | 支持在队头、队尾插入和删除 |
| 表达式求值 | 使用栈管理操作数及运算过程 |
| 数组 | 使用下标访问元素 |
| 对称矩阵 | 利用 (a*{ij}=a*{ji}) 压缩存储 |
| 稀疏矩阵 | 重点存储非零元素及其位置信息 |