mirror of
https://github.com/happyflyer/wangdao-data-structure.git
synced 2026-04-29 21:22:09 +08:00
栈和队列
三要素:
- 逻辑结构
- 数据的运算
- 存储结构(物理结构)
栈和队列都是操作受限的线性表。
1. 栈
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*9*8*7*6}{6*5*4*3*2*1}
=42
1.3. 顺序栈
顺序存储,用静态数据实现,并需要记录栈顶指针。
基本操作
- 初始化(创)
- 进栈(增)
- 出栈(删)
- 获取栈顶元素(查询)
两种实现
- 初始化时
top=-1 - 初始化时
top=0
共享栈
- 两个栈共享同一片内存空间,两个栈从两边往中间增长。
- 初始化:
S.top0 = -1;,S.top1 = MaxSize;。 - 栈满条件:
S.top0 + 1 == S.top1;
1.4. 链栈
与单链表类似,只实现头插法、头结点出栈。
- 带头结点的链栈
- 不带头结点的链栈
2. 队列
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. 队列的顺序实现
实现思想:
- 用静态数组存放数据元素,设置队头/队尾指针。
- 循环队列:用模运算(取余)将存储空间在逻辑上变为环状。
Q.rear = (Q.rear + 1) % MaxSize;。
重要考点:
- 如何初始化、入队、出队。
- 如何判空、判满。
- 如何计算队列的长度。
分析思路:
- 确定
front,rear指针的指向。rear指向队尾元素的后一个位置。rear指向队尾元素。
- 确定判空、判满的方法。
- 牺牲一个存储单元。
- 增加
size变量记录队列长度。 - 增加
tag变量用于标记最近一次操作是入队还是出队操作。 - ...
2.4. 队列的链式实现
实现方式:
- 带头结点
- 不带头结点
基本操作:
- 初始化队列
- 入队
- 出队
- 获取队头元素
- 判空
- 判满?不存在的
获取队列长度,时间复杂度 $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 | ||
| 1,2,4,3 | 2,1,4,3 | ||
| 1,3,2,4 | 2,3,1,4 | 3,2,1,4 | |
| 1,3,4,2 | 2,3,4,1 | 3,2,4,1 | |
| 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 | |
| 1,3,4,2 | 2,3,4,1 | 3,2,4,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 | |
| 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 | |
| 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. 栈的应用
4.1. 括号匹配
实现思路:
- 依次扫描所有字符。
- 遇到左括号入栈。
- 遇到右括号则弹出栈顶元素,检查是否匹配。
匹配失败的情况:
- 左括号单身:所有括号都检查完了,但是栈非空。
- 右括号单身:扫描到右括号,但是此时栈空。
- 左右括号不匹配:栈顶左括号,与当前的右括号不匹配。
4.2. 表达式求值
三种算数表达式:
- 中缀表达式
- 后缀表达式
- 前缀表达式
后缀表达式相关考点:
- 中缀表达式转后缀表达式
- 后缀表达式求值
前缀表达式相关考点:
- 中缀表达式转前缀表达式
- 前缀表达式求值
中缀表达式求值 = 中缀转后缀 + 后缀表达式求值
4.3. 递归
实现递归表达式:
- 递归表达式(递归体)
- 边界条件(递归出口)
递归缺点:
- 太多层递归可能会导致栈溢出。
- 可能包含很多重复计算。