mirror of
https://github.com/happyflyer/wangdao-data-structure.git
synced 2026-05-02 06:31:40 +08:00
320 lines
9.0 KiB
Markdown
320 lines
9.0 KiB
Markdown
# 栈和队列
|
||
|
||
三要素:
|
||
|
||
1. 逻辑结构
|
||
2. 数据的运算
|
||
3. 存储结构(物理结构)
|
||
|
||
栈和队列都是**操作受限**的线性表。
|
||
|
||
## 1. [栈](stack/README.md#栈)
|
||
|
||
### 1.1. 定义
|
||
|
||
栈是**只允许在一端进行插入或删除操作**的线性表。
|
||
|
||
- 栈顶:允许插入和删除的一端。
|
||
- 栈底:不允许插入和删除的一端。
|
||
- 空栈
|
||
- 栈顶元素
|
||
- 栈底元素
|
||
|
||
进栈顺序:
|
||
|
||
$$
|
||
a_1->a_2->a_3->a_4->a_5
|
||
$$
|
||
|
||
出栈顺序:
|
||
|
||
$$
|
||
a_5->a_4->a_3->a_2->a_1
|
||
$$
|
||
|
||
后进先出,Last In First OUT(LIFO)
|
||
|
||
### 1.2. 基本操作
|
||
|
||
- `InitStack(&S)`:初始化栈。构造一个空栈 $S$,分配内存空间。
|
||
- `DestroyStack(&S)`:销毁栈。销毁并释放栈 $S$ 所占用的内存空间。
|
||
- `Push(&S, x)`:进栈。若栈 $S$ 未满,则将 $x$ 加入使之成为新栈顶。
|
||
- `Pop(&S, &x)`:出栈,若栈 $S$ 非空,则弹出栈顶元素,并用 $x$ 返回。
|
||
- `GetTop(S, &x)`:读栈顶元素。若栈 $S$ 非空,则用 $x$ 返回栈顶元素。
|
||
- `StackEmpty(S)`:判断一个栈 $S$ 是否为空。若 $S$ 为空,则返回 `true`,否则返回 `false`。
|
||
|
||
$n$ 个不同的元素进栈,出栈元素的不同排列的个数为:
|
||
|
||
$$
|
||
\frac{1}{n+1} C^{n}_{2n}
|
||
$$
|
||
|
||
上述公式称为 **卡特兰(Catalan)数**。可采用数学归纳法证明(不要求掌握)。
|
||
|
||
$$
|
||
\frac{1}{5+1} C^{5}_{10}
|
||
=\frac{10 \times 9 \times 8 \times 7 \times 6}{6 \times 5 \times 4 \times 3 \times 2 \times 1}
|
||
=42
|
||
$$
|
||
|
||
### 1.3. [顺序栈](stack/README.md#1-顺序栈)
|
||
|
||
顺序存储,用静态数据实现,并需要记录栈顶指针。
|
||
|
||
基本操作
|
||
|
||
- [初始化(创)](stack/README.md#12-初始化栈)
|
||
- [进栈(增)](stack/README.md#13-进入栈)
|
||
- [出栈(删)](stack/README.md#14-出栈)
|
||
- [获取栈顶元素(查询)](stack/README.md#15-获取栈顶元素)
|
||
|
||
两种实现
|
||
|
||
- 初始化时 `top=-1`
|
||
- 初始化时 `top=0`
|
||
|
||
[共享栈](stack/README.md#18-共享栈)
|
||
|
||
- 两个栈共享同一片内存空间,两个栈从两边往中间增长。
|
||
- 初始化:`S.top0 = -1;`,`S.top1 = MaxSize;`。
|
||
- 栈满条件:`S.top0 + 1 == S.top1;`
|
||
|
||
### 1.4. [链栈](stack/README.md#2-链栈)
|
||
|
||
与单链表类似,只实现头插法、头结点出栈。
|
||
|
||
- 带头结点的链栈
|
||
- 不带头结点的链栈
|
||
|
||
## 2. [队列](queue/README.md#队列)
|
||
|
||
### 2.1. 定义
|
||
|
||
队列是**只允许在一端进行插入,在另一端删除操作**的线性表。
|
||
|
||
- 队头:允许删除的一端。
|
||
- 队尾:允许插入的一端。
|
||
- 空队列
|
||
- 队头元素
|
||
- 队尾元素
|
||
|
||
入队顺序:
|
||
|
||
$$
|
||
a_1->a_2->a_3->a_4->a_5
|
||
$$
|
||
|
||
出队顺序:
|
||
|
||
$$
|
||
a_1->a_2->a_3->a_4->a_5
|
||
$$
|
||
|
||
先进先出,First In First OUT(FIFO)
|
||
|
||
### 2.2. 基本操作
|
||
|
||
- `InitQueue(&Q)`:初始化队列。构造一个空队列 $Q$,分配内存空间。
|
||
- `DestroyQueue(&Q)`:销毁队列。销毁并释放队列 $Q$ 所占用的内存空间。
|
||
- `EnQueue(&Q, x)`:入队。若队列 $Q$ 未满,则将 $x$ 加入使之成为新的队尾元素。
|
||
- `DeQueue(&Q, &x)`:出队,若队列 $Q$ 非空,则弹出队头元素,并用 $x$ 返回。
|
||
- `GetHead(Q, &x)`:读队头元素。若队列 $Q$ 非空,则用 $x$ 返回队头元素。
|
||
- `QueueEmpty(Q)`:判断一个队列 $Q$ 是否为空。若 $Q$ 为空,则返回 `true`,否则返回 `false`。
|
||
|
||
### 2.3. [队列的顺序实现](queue/README.md#1-队列的顺序实现)
|
||
|
||
实现思想:
|
||
|
||
- 用静态数组存放数据元素,设置队头/队尾指针。
|
||
- 循环队列:用模运算(取余)将存储空间在逻辑上变为环状。
|
||
- `Q.rear = (Q.rear + 1) % MaxSize;`。
|
||
|
||
重要考点:
|
||
|
||
- 如何初始化、入队、出队。
|
||
- 如何判空、判满。
|
||
- 如何计算队列的长度。
|
||
|
||
分析思路:
|
||
|
||
- 确定 `front`,`rear` 指针的指向。
|
||
- `rear` 指向队尾元素的后一个位置。
|
||
- `rear` 指向队尾元素。
|
||
- 确定判空、判满的方法。
|
||
- 牺牲一个存储单元。
|
||
- 增加 `size` 变量记录队列长度。
|
||
- 增加 `tag` 变量用于标记最近一次操作是入队还是出队操作。
|
||
- ...
|
||
|
||
### 2.4. [队列的链式实现](queue/README.md#2-队列的链式实现)
|
||
|
||
实现方式:
|
||
|
||
- 带头结点
|
||
- 不带头结点
|
||
|
||
基本操作:
|
||
|
||
- 初始化队列
|
||
- 入队
|
||
- 出队
|
||
- 获取队头元素
|
||
- 判空
|
||
- 判满?不存在的
|
||
|
||
获取队列长度,时间复杂度 $O(n)$。
|
||
|
||
如果频繁使用队列长度,可以设置一个变量 `length`。
|
||
|
||
## 3. 双端队列
|
||
|
||
### 3.1. 定义
|
||
|
||
双端队列:只允许从**两端插入**、**两端删除**的线性表。
|
||
|
||
输入受限的双端队列:只允许从**一端插入**、**两端删除**的线性表。
|
||
|
||
输出首先的双端队列:只允许从**两端插入**、**一端删除**的线性表。
|
||
|
||
### 3.2. 问题
|
||
|
||
若数据元素输入序列为:1,2,3,4,则哪些输出序列是合法的?哪些是非法的?
|
||
|
||
$$
|
||
A^{4}_{4}=4!=24
|
||
$$
|
||
|
||
#### 3.2.1. 对栈合法的序列
|
||
|
||
| 1,x,x,x | 2,x,x,x | 3,x,x,x | 4,x,x,x |
|
||
| ----------- | ----------- | ----------- | ----------- |
|
||
| 1,2,3,4 | 2,1,3,4 | ~~3,1,2,4~~ | ~~4,1,2,3~~ |
|
||
| 1,2,4,3 | 2,1,4,3 | ~~3,1,4,2~~ | ~~4,1,3,2~~ |
|
||
| 1,3,2,4 | 2,3,1,4 | 3,2,1,4 | ~~4,2,1,3~~ |
|
||
| 1,3,4,2 | 2,3,4,1 | 3,2,4,1 | ~~4,2,3,1~~ |
|
||
| ~~1,4,2,3~~ | ~~2,4,1,1~~ | ~~3,4,1,2~~ | ~~4,3,1,2~~ |
|
||
| 1,5,3,2 | 2,4,3,3 | 3,4,2,1 | 4,3,2,1 |
|
||
|
||
卡特兰数:
|
||
|
||
$$
|
||
\frac{1}{n+1} C^{n}_{2n}
|
||
=\frac{1}{4+1} C^{5}_{8}
|
||
=14
|
||
$$
|
||
|
||
#### 3.2.2. 对输入受限的双端队列合法的序列
|
||
|
||
| 1,x,x,x | 2,x,x,x | 3,x,x,x | 4,x,x,x |
|
||
| ------- | ------- | ------- | ----------- |
|
||
| 1,2,3,4 | 2,1,3,4 | 3,1,2,4 | 4,1,2,3 |
|
||
| 1,2,4,3 | 2,1,4,3 | 3,1,4,2 | 4,1,3,2 |
|
||
| 1,3,2,4 | 2,3,1,4 | 3,2,1,4 | ~~4,2,1,3~~ |
|
||
| 1,3,4,2 | 2,3,4,1 | 3,2,4,1 | ~~4,2,3,1~~ |
|
||
| 1,4,2,3 | 2,4,1,1 | 3,4,1,2 | 4,3,1,2 |
|
||
| 1,5,3,2 | 2,4,3,3 | 3,4,2,1 | 4,3,2,1 |
|
||
|
||
#### 3.2.3. 对输出受限的双端队列合法的序列
|
||
|
||
| 1,x,x,x | 2,x,x,x | 3,x,x,x | 4,x,x,x |
|
||
| ------- | ------- | ------- | ----------- |
|
||
| 1,2,3,4 | 2,1,3,4 | 3,1,2,4 | 4,1,2,3 |
|
||
| 1,2,4,3 | 2,1,4,3 | 3,1,4,2 | ~~4,1,3,2~~ |
|
||
| 1,3,2,4 | 2,3,1,4 | 3,2,1,4 | 4,2,1,3 |
|
||
| 1,3,4,2 | 2,3,4,1 | 3,2,4,1 | ~~4,2,3,1~~ |
|
||
| 1,4,2,3 | 2,4,1,1 | 3,4,1,2 | 4,3,1,2 |
|
||
| 1,5,3,2 | 2,4,3,3 | 3,4,2,1 | 4,3,2,1 |
|
||
|
||
## 4. [栈的应用](stack-applications/README.md#栈的应用)
|
||
|
||
### 4.1. [括号匹配](stack-applications/README.md#1-括号匹配)
|
||
|
||
实现思路:
|
||
|
||
1. 依次扫描所有字符。
|
||
2. 遇到左括号入栈。
|
||
3. 遇到右括号则弹出栈顶元素,检查是否匹配。
|
||
|
||
匹配失败的情况:
|
||
|
||
- 左括号单身:所有括号都检查完了,但是栈非空。
|
||
- 右括号单身:扫描到右括号,但是此时栈空。
|
||
- 左右括号不匹配:栈顶左括号,与当前的右括号不匹配。
|
||
|
||
### 4.2. [表达式求值](stack-applications/README.md#2-表达式求值)
|
||
|
||
三种算数表达式:
|
||
|
||
- 中缀表达式
|
||
- 后缀表达式
|
||
- 前缀表达式
|
||
|
||
后缀表达式相关考点:
|
||
|
||
- **中缀表达式转后缀表达式**
|
||
- **后缀表达式求值**
|
||
|
||
前缀表达式相关考点:
|
||
|
||
- 中缀表达式转前缀表达式
|
||
- 前缀表达式求值
|
||
|
||
$$
|
||
中缀表达式求值 = 中缀转后缀 + 后缀表达式求值
|
||
$$
|
||
|
||
### 4.3. [递归](stack-applications/README.md#3-递归)
|
||
|
||
实现递归表达式:
|
||
|
||
- 递归表达式(递归体)
|
||
- 边界条件(递归出口)
|
||
|
||
递归缺点:
|
||
|
||
- 太多层递归可能会导致栈溢出。
|
||
- 可能包含很多重复计算。
|
||
|
||
## 5. 队列的应用
|
||
|
||
- 树的层次遍历
|
||
- 图的广度优先遍历
|
||
- 多个进程争抢着使用优先的系统资源时,FCFS(First Come First Service,先来先服务)是一种常用策略。
|
||
|
||
## 6. [矩阵的压缩存储](matrix/README.md#矩阵的压缩存储)
|
||
|
||
数组的存储结构:
|
||
|
||
- [一维数组](matrix/README.md#1-一维数组)
|
||
- [二维数组](matrix/README.md#2-二维数组)
|
||
|
||
特殊矩阵:
|
||
|
||
- [对称矩阵](matrix/README.md#3-对称矩阵)
|
||
- 特点:对方阵中的任意一个元素,有 $a_{i,j}=a_{j,i}$。
|
||
- 压缩:只存主对角线+下三角区(或主对角线+上三角区)。
|
||
- [三角矩阵](matrix/README.md#4-三角矩阵)
|
||
- 特点:上三角区全为常量(下三角矩阵);或下三角区全为常量(上三角矩阵)。
|
||
- 压缩:按行优先/列优先规则依次存储非常量区域,并在最后一个位置存放常量 $c$。
|
||
- [三对角矩阵](matrix/README.md#5-三对角矩阵)
|
||
- 特点:当 $|i-j|>1$ 时,$a_{i,j}=0$ ($1 \leq i, j \leq n$)。
|
||
- 压缩:按行优先/列优先规则依次存储带状区域。
|
||
- [稀疏矩阵](matrix/README.md#6-稀疏矩阵)
|
||
- 特点:非零元素个数远少于零元素个数。
|
||
- 压缩:只存储非零元素
|
||
- 顺序存储:顺序存储三元组 `<行,列,值>`。
|
||
- 链式存储:十字链表法。
|
||
|
||
常见考题:
|
||
|
||
- 矩阵的压缩存储需要多长的数组。
|
||
- 由矩阵行列号 $<i,j>$ 推出对应的数组下标号 $k$。
|
||
- 由 $k$ 推出 $<i,j>$
|
||
- 如何处理不等式中的“刚好大于等于/小于等于”。
|
||
- “向上取整/向下取整”。
|
||
- 易错点:
|
||
- 存储上三角?下三角?
|
||
- 行优先?列优先?
|
||
- 矩阵元素的下标从 0?1?开始
|
||
- 数组下标从 0?1?开始
|