mirror of
https://github.com/happyflyer/wangdao-data-structure.git
synced 2026-02-03 02:24:39 +08:00
完成栈的应用
This commit is contained in:
@@ -182,6 +182,7 @@ free(L.data);
|
||||
**问题**:
|
||||
|
||||
请描述顺序表和链表的 bla bla bla
|
||||
|
||||
实现线性表时,用顺序表还是链表好?
|
||||
|
||||
**答题思路**:
|
||||
|
||||
202
ch3/README.md
202
ch3/README.md
@@ -6,7 +6,9 @@
|
||||
2. 数据的运算
|
||||
3. 存储结构(物理结构)
|
||||
|
||||
## 1. 栈
|
||||
栈和队列都是**操作受限**的线性表。
|
||||
|
||||
## 1. [栈](stack/README.md)
|
||||
|
||||
### 1.1. 定义
|
||||
|
||||
@@ -37,7 +39,7 @@ $$
|
||||
- `InitStack(&S)`:初始化栈。构造一个空栈 $S$,分配内存空间。
|
||||
- `DestroyStack(&S)`:销毁栈。销毁并释放栈 $S$ 所占用的内存空间。
|
||||
- `Push(&S, x)`:进栈。若栈 $S$ 未满,则将 $x$ 加入使之成为新栈顶。
|
||||
- `Pop(&S, &x)`:出战,若栈 $S$ 非空,则弹出栈顶元素,并用 $x$ 返回。
|
||||
- `Pop(&S, &x)`:出栈,若栈 $S$ 非空,则弹出栈顶元素,并用 $x$ 返回。
|
||||
- `GetTop(S, &x)`:读栈顶元素。若栈 $S$ 非空,则用 $x$ 返回栈顶元素。
|
||||
- `StackEmpty(S)`:判断一个栈 $S$ 是否为空。若 $S$ 为空,则返回 `true`,否则返回 `false`。
|
||||
|
||||
@@ -47,7 +49,7 @@ $$
|
||||
\frac{1}{n+1} C^{n}_{2n}
|
||||
$$
|
||||
|
||||
上述公式称为 卡特兰(Catalan)数。可采用数学归纳法证明(不要求掌握)。
|
||||
上述公式称为 **卡特兰(Catalan)数**。可采用数学归纳法证明(不要求掌握)。
|
||||
|
||||
$$
|
||||
\frac{1}{5+1} C^{5}_{10}
|
||||
@@ -78,3 +80,197 @@ $$
|
||||
- 栈满条件:`S.top0 + 1 == S.top1;`
|
||||
|
||||
### 1.4. [链栈](stack/README.md)
|
||||
|
||||
与单链表类似,只实现头插法、头结点出栈。
|
||||
|
||||
- 带头结点的链栈
|
||||
- 不带头结点的链栈
|
||||
|
||||
## 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)
|
||||
|
||||
实现思想:
|
||||
|
||||
- 用静态数组存放数据元素,设置队头/队尾指针。
|
||||
- 循环队列:用模运算(取余)将存储空间在逻辑上变为环状。
|
||||
- `Q.rear = (Q.rear + 1) % MaxSize;`。
|
||||
|
||||
重要考点:
|
||||
|
||||
- 如何初始化、入队、出队。
|
||||
- 如何判空、判满。
|
||||
- 如何计算队列的长度。
|
||||
|
||||
分析思路:
|
||||
|
||||
- 确定 `front`,`rear` 指针的指向。
|
||||
- `rear` 指向队尾元素的后一个位置。
|
||||
- `rear` 指向队尾元素。
|
||||
- 确定判空、判满的方法。
|
||||
- 牺牲一个存储单元。
|
||||
- 增加 `size` 变量记录队列长度。
|
||||
- 增加 `tag` 变量用于标记最近一次操作是入队还是出队操作。
|
||||
- ...
|
||||
|
||||
### 2.4. [队列的链式实现](queue/README.md)
|
||||
|
||||
实现方式:
|
||||
|
||||
- 带头结点
|
||||
- 不带头结点
|
||||
|
||||
基本操作:
|
||||
|
||||
- 初始化队列
|
||||
- 入队
|
||||
- 出队
|
||||
- 获取队头元素
|
||||
- 判空
|
||||
- 判满?不存在的
|
||||
|
||||
获取队列长度,时间复杂度 $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. 依次扫描所有字符。
|
||||
2. 遇到左括号入栈。
|
||||
3. 遇到右括号则弹出栈顶元素,检查是否匹配。
|
||||
|
||||
匹配失败的情况:
|
||||
|
||||
- 左括号单身:所有括号都检查完了,但是栈非空。
|
||||
- 右括号单身:扫描到右括号,但是此时栈空。
|
||||
- 左右括号不匹配:栈顶左括号,与当前的右括号不匹配。
|
||||
|
||||
### 4.2. [表达式求值](stack-applications/README.md)
|
||||
|
||||
三种算数表达式:
|
||||
|
||||
- 中缀表达式
|
||||
- 后缀表达式
|
||||
- 前缀表达式
|
||||
|
||||
后缀表达式相关考点:
|
||||
|
||||
- **中缀表达式转后缀表达式**
|
||||
- **后缀表达式求值**
|
||||
|
||||
前缀表达式相关考点:
|
||||
|
||||
- 中缀表达式转前缀表达式
|
||||
- 前缀表达式求值
|
||||
|
||||
$$
|
||||
中缀表达式求值 = 中缀转后缀 + 后缀表达式求值
|
||||
$$
|
||||
|
||||
### 4.3. [递归](stack-applications/README.md)
|
||||
|
||||
实现递归表达式:
|
||||
|
||||
- 递归表达式(递归体)
|
||||
- 边界条件(递归出口)
|
||||
|
||||
递归缺点:
|
||||
|
||||
- 太多层递归可能会导致栈溢出。
|
||||
- 可能包含很多重复计算。
|
||||
|
||||
338
ch3/queue/README.md
Normal file
338
ch3/queue/README.md
Normal file
@@ -0,0 +1,338 @@
|
||||
# 队列
|
||||
|
||||
## 1. 队列的顺序实现
|
||||
|
||||
### 1.1. 定义
|
||||
|
||||
用顺序存储方式实现的队列。
|
||||
|
||||
```cpp
|
||||
#define MaxSize 10
|
||||
typedef struct
|
||||
{
|
||||
ElemType data[MaxSize];
|
||||
int front, rear; // 队头、队尾指针
|
||||
} SqQueue;
|
||||
```
|
||||
|
||||
$MaxSize*sizeof(ElemType)+8B$
|
||||
|
||||
### 1.2. 初始化队列
|
||||
|
||||
```cpp
|
||||
// 初始化队列
|
||||
void InitQueue(SqQueue &Q)
|
||||
{
|
||||
Q.front = Q.rear = 0;
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// 判断队列是否为空
|
||||
bool QueueEmpty(SqQueue Q)
|
||||
{
|
||||
return Q.rear == Q.front;
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// 判断队列是否已满
|
||||
bool QueueFull(SqQueue Q)
|
||||
{
|
||||
return (Q.rear + 1) % MaxSize == Q.front;
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// 获取队长
|
||||
int Length(SqQueue Q)
|
||||
{
|
||||
return (Q.rear + MaxSize - Q.front) % MaxSize;
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3. 入队
|
||||
|
||||
队尾指针指向队尾元素的下一个位置。
|
||||
|
||||
```cpp
|
||||
// 入队
|
||||
bool EnQueue(SqQueue &Q, int x)
|
||||
{
|
||||
if (QueueFull(Q))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Q.data[Q.rear] = x;
|
||||
Q.rear = (Q.rear + 1) % MaxSize;
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### 1.4. 出队
|
||||
|
||||
```cpp
|
||||
// 出队
|
||||
bool DeQueue(SqQueue &Q, int x)
|
||||
{
|
||||
if (QueueEmpty(Q))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
x = Q.data[Q.front];
|
||||
Q.front = (Q.front + 1) % MaxSize;
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### 1.5. 获取队头元素
|
||||
|
||||
```cpp
|
||||
// 读取队头元素
|
||||
bool GetHead(SqQueue &Q, int &x)
|
||||
{
|
||||
if (QueueEmpty(Q))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
x = Q.data[Q.front];
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### 1.6. 不要浪费最后一个空位
|
||||
|
||||
加入辅助变量。
|
||||
|
||||
#### 1.6.1. size
|
||||
|
||||
```cpp
|
||||
#define MaxSize 10
|
||||
typedef struct
|
||||
{
|
||||
ElemType data[MaxSize];
|
||||
int front, rear;
|
||||
int size;
|
||||
} SqQueue;
|
||||
```
|
||||
|
||||
- 初始时:`Q.front = Q.rear = 0;`,`Q.size = 0;`
|
||||
- 入队:`Q.size++;`
|
||||
- 出队:`Q.size--;`
|
||||
- 队满条件:`Q.size == MaxSize;`
|
||||
- 队空条件:`Q.size == 0;`
|
||||
|
||||
#### 1.6.2. tag
|
||||
|
||||
```cpp
|
||||
#define MaxSize 10
|
||||
typedef struct
|
||||
{
|
||||
ElemType data[MaxSize];
|
||||
int front, rear;
|
||||
int tag;
|
||||
} SqQueue;
|
||||
```
|
||||
|
||||
- 初始时:`Q.front = Q.rear = 0;`,`Q.tag = 0;`
|
||||
- 入队:`Q.tag = 1;`,只有入队操作,才能导致队满。
|
||||
- 出队:`Q.tag = 0;`,只有出队操作,才能导致队空。
|
||||
- 队满条件:`Q.front == Q.rear && Q.tag == 1;`
|
||||
- 队空条件:`Q.front == Q.rear && Q.tag == 0;`
|
||||
|
||||
### 1.7. 队尾指针指向队尾元素
|
||||
|
||||
入队:
|
||||
|
||||
```cpp
|
||||
Q.rear = (Q.rear + 1) % MaxSize;
|
||||
Q.data[Q.rear] = x;
|
||||
```
|
||||
|
||||
初始化:
|
||||
|
||||
```cpp
|
||||
Q.front = 0;
|
||||
Q.rear = MaxSize - 1;
|
||||
```
|
||||
|
||||
判空:
|
||||
|
||||
```cpp
|
||||
(Q.rear + 1) % MaxSize == Q.front;
|
||||
```
|
||||
|
||||
判满(牺牲一个存储单元):
|
||||
|
||||
```cpp
|
||||
(Q.rear + 2) % MaxSize == Q.front;
|
||||
```
|
||||
|
||||
## 2. 队列的链式实现
|
||||
|
||||
### 2.1. 定义
|
||||
|
||||
```cpp
|
||||
typedef struct LNode
|
||||
{
|
||||
ElemType data;
|
||||
struct LNode *next;
|
||||
} LNode;
|
||||
typedef struct
|
||||
{
|
||||
LNode *front, *rear; // 队列的队头、队尾指针
|
||||
} LinkQueue;
|
||||
```
|
||||
|
||||
### 2.2. 初始化
|
||||
|
||||
```cpp
|
||||
// 初始化队列
|
||||
bool InitQueue(LinkQueue &Q)
|
||||
{
|
||||
LNode *s = (LNode *)malloc(sizeof(LNode));
|
||||
if (s == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
s->next = NULL;
|
||||
Q.front = Q.rear = s;
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// 判断队列是否为空
|
||||
bool QueueEmpty(LinkQueue Q)
|
||||
{
|
||||
return Q.rear == Q.front;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3. 入队
|
||||
|
||||
```cpp
|
||||
// 入队
|
||||
bool EnQueue(LinkQueue &Q, int x)
|
||||
{
|
||||
LNode *s = (LNode *)malloc(sizeof(LNode));
|
||||
s->data = x;
|
||||
s->next = NULL;
|
||||
Q.rear->next = s;
|
||||
Q.rear = s;
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4. 出队
|
||||
|
||||
```cpp
|
||||
// 出队
|
||||
bool DeQueue(LinkQueue &Q, int x)
|
||||
{
|
||||
if (QueueEmpty(Q))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LNode *q = Q.front->next;
|
||||
x = q->data;
|
||||
Q.front->next = q->next;
|
||||
if (Q.rear == q) // 如果是最后一个结点出队
|
||||
{
|
||||
Q.rear = Q.front;
|
||||
}
|
||||
free(q);
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5. 获取队头元素
|
||||
|
||||
```cpp
|
||||
// 读取队头元素
|
||||
bool GetHead(LinkQueue &Q, int &x)
|
||||
{
|
||||
if (QueueEmpty(Q))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LNode *q = Q.front->next;
|
||||
x = q->data;
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.6. 不带头结点
|
||||
|
||||
```cpp
|
||||
// 初始化队列
|
||||
bool InitQueue(LinkQueue &Q)
|
||||
{
|
||||
Q.front = Q.rear = NULL;
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// 判断队列是否为空
|
||||
bool QueueEmpty(LinkQueue Q)
|
||||
{
|
||||
return Q.front = NULL;
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// 入队
|
||||
bool EnQueue(LinkQueue &Q, int x)
|
||||
{
|
||||
LNode *s = (LNode *)malloc(sizeof(LNode));
|
||||
s->data = x;
|
||||
s->next = NULL;
|
||||
if (Q.front == NULL)
|
||||
{
|
||||
Q.front = s;
|
||||
Q.rear = s;
|
||||
}
|
||||
else
|
||||
{
|
||||
Q.rear->next = s;
|
||||
Q.rear = s;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// 出队
|
||||
bool DeQueue(LinkQueue &Q, int x)
|
||||
{
|
||||
if (QueueEmpty(Q))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LNode *q = Q.front;
|
||||
x = q->data;
|
||||
Q.front = q->next;
|
||||
if (Q.rear == q) // 如果是最后一个结点出队
|
||||
{
|
||||
Q.front = NULL;
|
||||
Q.rear = NULL;
|
||||
}
|
||||
free(q);
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// 读取队头元素
|
||||
bool GetHead(LinkQueue &Q, int &x)
|
||||
{
|
||||
if (QueueEmpty(Q))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LNode *q = Q.front;
|
||||
x = q->data;
|
||||
return true;
|
||||
}
|
||||
```
|
||||
100
ch3/queue/link-with.cpp
Normal file
100
ch3/queue/link-with.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
typedef struct LNode
|
||||
{
|
||||
int data;
|
||||
struct LNode *next;
|
||||
} LNode;
|
||||
typedef struct
|
||||
{
|
||||
LNode *front, *rear; // 队列的队头、队尾指针
|
||||
} LinkQueue;
|
||||
|
||||
// 初始化队列,带头结点
|
||||
bool InitQueue(LinkQueue &Q)
|
||||
{
|
||||
LNode *s = (LNode *)malloc(sizeof(LNode));
|
||||
if (s == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
s->next = NULL;
|
||||
Q.front = Q.rear = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 判断队列是否为空
|
||||
bool QueueEmpty(LinkQueue Q)
|
||||
{
|
||||
return Q.rear == Q.front;
|
||||
}
|
||||
|
||||
// 入队
|
||||
bool EnQueue(LinkQueue &Q, int x)
|
||||
{
|
||||
LNode *s = (LNode *)malloc(sizeof(LNode));
|
||||
s->data = x;
|
||||
s->next = NULL;
|
||||
Q.rear->next = s;
|
||||
Q.rear = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 出队
|
||||
bool DeQueue(LinkQueue &Q, int x)
|
||||
{
|
||||
if (QueueEmpty(Q))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LNode *q = Q.front->next;
|
||||
x = q->data;
|
||||
Q.front->next = q->next;
|
||||
if (Q.rear == q) // 如果是最后一个结点出队
|
||||
{
|
||||
Q.rear = Q.front;
|
||||
}
|
||||
free(q);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 读取队头元素
|
||||
bool GetHead(LinkQueue &Q, int &x)
|
||||
{
|
||||
if (QueueEmpty(Q))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LNode *q = Q.front->next;
|
||||
x = q->data;
|
||||
return true;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
LinkQueue Q;
|
||||
InitQueue(Q);
|
||||
EnQueue(Q, 1);
|
||||
EnQueue(Q, 2);
|
||||
EnQueue(Q, 3);
|
||||
EnQueue(Q, 4);
|
||||
EnQueue(Q, 5);
|
||||
// 打印操作
|
||||
LNode *i = Q.front->next;
|
||||
while (i != NULL)
|
||||
{
|
||||
printf("%d\n", i->data);
|
||||
i = i->next;
|
||||
}
|
||||
int x;
|
||||
DeQueue(Q, x);
|
||||
// printf("%d\n", x);
|
||||
// 打印操作
|
||||
i = Q.front->next;
|
||||
while (i != NULL)
|
||||
{
|
||||
printf("%d\n", i->data);
|
||||
i = i->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
103
ch3/queue/link-without.cpp
Normal file
103
ch3/queue/link-without.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
typedef struct LNode
|
||||
{
|
||||
int data;
|
||||
struct LNode *next;
|
||||
} LNode;
|
||||
typedef struct
|
||||
{
|
||||
LNode *front, *rear; // 队列的队头、队尾指针
|
||||
} LinkQueue;
|
||||
|
||||
// 初始化队列,不带头结点
|
||||
bool InitQueue(LinkQueue &Q)
|
||||
{
|
||||
Q.front = Q.rear = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 判断队列是否为空
|
||||
bool QueueEmpty(LinkQueue Q)
|
||||
{
|
||||
return Q.front = NULL;
|
||||
}
|
||||
|
||||
// 入队
|
||||
bool EnQueue(LinkQueue &Q, int x)
|
||||
{
|
||||
LNode *s = (LNode *)malloc(sizeof(LNode));
|
||||
s->data = x;
|
||||
s->next = NULL;
|
||||
if (Q.front == NULL)
|
||||
{
|
||||
Q.front = s;
|
||||
Q.rear = s;
|
||||
}
|
||||
else
|
||||
{
|
||||
Q.rear->next = s;
|
||||
Q.rear = s;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 出队
|
||||
bool DeQueue(LinkQueue &Q, int x)
|
||||
{
|
||||
if (QueueEmpty(Q))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LNode *q = Q.front;
|
||||
x = q->data;
|
||||
Q.front = q->next;
|
||||
if (Q.rear == q) // 如果是最后一个结点出队
|
||||
{
|
||||
Q.front = NULL;
|
||||
Q.rear = NULL;
|
||||
}
|
||||
free(q);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 读取队头元素
|
||||
bool GetHead(LinkQueue &Q, int &x)
|
||||
{
|
||||
if (QueueEmpty(Q))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LNode *q = Q.front;
|
||||
x = q->data;
|
||||
return true;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
LinkQueue Q;
|
||||
InitQueue(Q);
|
||||
EnQueue(Q, 1);
|
||||
EnQueue(Q, 2);
|
||||
EnQueue(Q, 3);
|
||||
EnQueue(Q, 4);
|
||||
EnQueue(Q, 5);
|
||||
// 打印操作
|
||||
LNode *i = Q.front;
|
||||
while (i != NULL)
|
||||
{
|
||||
printf("%d\n", i->data);
|
||||
i = i->next;
|
||||
}
|
||||
int x;
|
||||
DeQueue(Q, x);
|
||||
// printf("%d\n", x);
|
||||
// 打印操作
|
||||
i = Q.front;
|
||||
while (i != NULL)
|
||||
{
|
||||
printf("%d\n", i->data);
|
||||
i = i->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
95
ch3/queue/sequence.cpp
Normal file
95
ch3/queue/sequence.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include <stdio.h>
|
||||
#define MaxSize 10
|
||||
typedef struct
|
||||
{
|
||||
int data[MaxSize];
|
||||
int front, rear; // 队头、队尾指针
|
||||
} SqQueue;
|
||||
|
||||
// 初始化队列
|
||||
void InitQueue(SqQueue &Q)
|
||||
{
|
||||
Q.front = Q.rear = 0;
|
||||
}
|
||||
|
||||
// 判断队列是否为空
|
||||
bool QueueEmpty(SqQueue Q)
|
||||
{
|
||||
return Q.rear == Q.front;
|
||||
}
|
||||
|
||||
// 判断队列是否已满
|
||||
bool QueueFull(SqQueue Q)
|
||||
{
|
||||
return (Q.rear + 1) % MaxSize == Q.front;
|
||||
}
|
||||
|
||||
// 获取队长
|
||||
int Length(SqQueue Q)
|
||||
{
|
||||
return (Q.rear + MaxSize - Q.front) % MaxSize;
|
||||
}
|
||||
|
||||
// 入队
|
||||
bool EnQueue(SqQueue &Q, int x)
|
||||
{
|
||||
if (QueueFull(Q))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Q.data[Q.rear] = x;
|
||||
Q.rear = (Q.rear + 1) % MaxSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 出队
|
||||
bool DeQueue(SqQueue &Q, int x)
|
||||
{
|
||||
if (QueueEmpty(Q))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
x = Q.data[Q.front];
|
||||
Q.front = (Q.front + 1) % MaxSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 读取队头元素
|
||||
bool GetHead(SqQueue &Q, int &x)
|
||||
{
|
||||
if (QueueEmpty(Q))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
x = Q.data[Q.front];
|
||||
return true;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
SqQueue Q;
|
||||
InitQueue(Q);
|
||||
EnQueue(Q, 1);
|
||||
EnQueue(Q, 2);
|
||||
EnQueue(Q, 3);
|
||||
EnQueue(Q, 4);
|
||||
EnQueue(Q, 5);
|
||||
// 打印操作
|
||||
int i = Q.front;
|
||||
while (i < Q.rear)
|
||||
{
|
||||
printf("%d\n", Q.data[i]);
|
||||
i = (i + 1) % MaxSize;
|
||||
}
|
||||
int x;
|
||||
DeQueue(Q, x);
|
||||
// printf("%d\n", x);
|
||||
// 打印操作
|
||||
i = Q.front;
|
||||
while (i < Q.rear)
|
||||
{
|
||||
printf("%d\n", Q.data[i]);
|
||||
i = (i + 1) % MaxSize;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
260
ch3/stack-applications/README.md
Normal file
260
ch3/stack-applications/README.md
Normal file
@@ -0,0 +1,260 @@
|
||||
# 栈的应用
|
||||
|
||||
## 1. 括号匹配
|
||||
|
||||
```cpp
|
||||
({([])}[])
|
||||
```
|
||||
|
||||
最后出现的左括号最先被匹配(后进先出,LIFO)。每出现一个右括号,就“消耗”(出栈)一个左括号。
|
||||
|
||||
- 遇到左括号就入栈。
|
||||
- 遇到右括号,就出栈一个左括号。
|
||||
|
||||

|
||||
|
||||
实现思路:
|
||||
|
||||
1. 依次扫描所有字符。
|
||||
2. 遇到左括号入栈。
|
||||
3. 遇到右括号则弹出栈顶元素,检查是否匹配。
|
||||
|
||||
匹配失败的情况:
|
||||
|
||||
- 左括号单身:所有括号都检查完了,但是栈非空。
|
||||
- 右括号单身:扫描到右括号,但是此时栈空。
|
||||
- 左右括号不匹配:栈顶左括号,与当前的右括号不匹配。
|
||||
|
||||
```cpp
|
||||
bool bracketCheck(char str[], int length)
|
||||
{
|
||||
SqStack S;
|
||||
InitStack(S);
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (str[i] == '(' || str[i] == '[' || str[i] == '{')
|
||||
{
|
||||
Push(S, str[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (StackEmpty(S))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
char topElem;
|
||||
Pop(S, topElem);
|
||||
if (str[i] == ')' && topElem != '(')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (str[i] == ']' && topElem != '[')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (str[i] == '}' && topElem != '{')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return StackEmpty(S);
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 表达式求值
|
||||
|
||||
$$
|
||||
((15 \div (7 - (1 + 1))) \times 3) - (2 + (1 + 1))
|
||||
$$
|
||||
|
||||
构成:
|
||||
|
||||
- 操作数
|
||||
- 运算符
|
||||
- 界限符
|
||||
|
||||
**后缀表达式** = 逆波兰表达式(Reverse Polish notation)
|
||||
|
||||
前缀表达式 = 波兰表达式(Polish notation)
|
||||
|
||||
| 中缀表达式 | 后缀表达式 | 前缀表达式 |
|
||||
| ------------------ | ------------------ | ------------------ |
|
||||
| $$a+b$$ | $$ab+$$ | $$+ab$$ |
|
||||
| $$a+b-c$$ | $$ab+c-$$ | $$-+abc$$ |
|
||||
| $$a+b-c \times d$$ | $$ab+cd \times -$$ | $$-+ab \times cd$$ |
|
||||
|
||||
### 2.1. 后缀表达式
|
||||
|
||||
**中缀转后缀**的手算方法:
|
||||
|
||||
1. 确定中缀表达式中各个运算符的运算顺序
|
||||
2. 选择下一个运算符,按照【左操作数 右操作数 运算符】的方式组合成一个新的操作数
|
||||
3. 如果还有运算符没被处理,转 2
|
||||
|
||||
```cpp
|
||||
// 中缀表达式
|
||||
((15 / (7 - (1 + 1))) * 3) - (2 + (1 + 1))
|
||||
// 后缀表达式
|
||||
15 7 1 1 + - / 3 * 2 1 1 + + -
|
||||
```
|
||||
|
||||
```cpp
|
||||
// 中缀表达式
|
||||
A + B * (C - D) - E / F
|
||||
// 后缀表达式
|
||||
A B C D - * + E F / -
|
||||
// 另一种后缀表达式
|
||||
A B C D - * E F / - +
|
||||
```
|
||||
|
||||
**“左优先”原则**:只要左边的运算符能先计算,就优先算左边的。引入“左优先”原则后,中缀转后缀的结果就是唯一的。
|
||||
|
||||
```cpp
|
||||
// 中缀表达式
|
||||
A + B - C * D / E + F
|
||||
// 后缀表达式
|
||||
A B + C D * E / - F +
|
||||
```
|
||||
|
||||
后缀表达式的手算方法:
|
||||
|
||||
从左往右扫描,每遇到一个操作符,就让运算符前面最近的两个操作数执行对应运算,合体为一个操作数。
|
||||
|
||||
用栈实现后缀表达式的计算:
|
||||
|
||||
1. 从左往右扫描下一个元素,直到处理完所有元素。
|
||||
2. 若扫描到操作数则压入栈,并回到 1,否则执行 3。
|
||||
3. 若扫描到运算符,则弹出两个栈顶元素,执行响应运算,运算结果压回栈顶,回到 1。
|
||||
|
||||
第三步中,先弹出右操作数,后弹出左操作数。
|
||||
|
||||
> 后缀表达式适用于基于栈的编程语言(stack-oriented programing language),如:Forth、PostScript。
|
||||
|
||||
### 2.2. 前缀表达式
|
||||
|
||||
**中缀转前缀**的手算方法:
|
||||
|
||||
1. 确定中缀表达式中各个运算符的运算顺序
|
||||
2. 选择下一个运算符,按照【运算符 左操作数 右操作数】的方式组合成一个新的操作数
|
||||
3. 如果还有运算符没被处理,转 2
|
||||
|
||||
**“右优先”原则**:只要右边的运算符能先计算,就优先算右边的。
|
||||
|
||||
```cpp
|
||||
// 中缀表达式
|
||||
A + B * (C - D) - E / F
|
||||
// 前缀表达式
|
||||
+ A - * B - C D / E F
|
||||
```
|
||||
|
||||
```cpp
|
||||
// 中缀表达式
|
||||
((15 / (7 - (1 + 1))) * 3) - (2 + (1 + 1))
|
||||
// 前缀表达式
|
||||
- * / 15 - 7 + 1 1 3 + 2 + 1 1
|
||||
```
|
||||
|
||||
用栈实现前缀表达式的计算:
|
||||
|
||||
1. 从右向左扫描下一个元素,直到处理完所有元素。
|
||||
2. 若扫描到操作数则压入栈,并回到 1,否则执行 3。
|
||||
3. 若扫描到运算符,则弹出两个栈顶元素,执行响应运算,运算结果压回栈顶,回到 1。
|
||||
|
||||
第三步中,先弹出左操作数,后弹出右操作数。
|
||||
|
||||
### 2.3. 用栈实现
|
||||
|
||||
#### 2.3.1. 中缀表达式转后缀表达式(机算)
|
||||
|
||||
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。
|
||||
|
||||
从左到右处理各个元素,知道末尾,可能遇到三种情况:
|
||||
|
||||
- 遇到操作数,直接加入后缀表达式。
|
||||
- 遇到界限符。
|
||||
- 遇到 "(" 直接入栈。
|
||||
- 遇到 ")" 则依次弹出站内运算符并加入后缀表达式,直到弹出 "(" 为止。"(" 不加入后缀表达式。
|
||||
- 遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到 "(" 或栈空则停止。之后再把当前运算符入栈。
|
||||
|
||||
按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。
|
||||
|
||||
#### 2.3.2. 中缀表达式求值(用栈实现)
|
||||
|
||||
$$
|
||||
中缀表达式求值 = 中缀转后缀 + 后缀表达式求值
|
||||
$$
|
||||
|
||||
初始化两个栈,操作数栈和运算符栈。
|
||||
|
||||
若扫描到操作数,压入操作数栈。
|
||||
|
||||
若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
|
||||
|
||||
### 2.4. 递归
|
||||
|
||||
#### 2.4.1. 函数调用
|
||||
|
||||
函数调用的特点:最后被调用的函数最先执行结束。(FIFO)
|
||||
|
||||
函数调用时,需要用一个栈存储:
|
||||
|
||||
1. 调用返回地址
|
||||
2. 实参
|
||||
3. 局部变量
|
||||
|
||||
#### 2.4.2. 递归算法
|
||||
|
||||
适合用“递归”算法解决:可以把原始问题转换为属性相同,但规模更小的问题。
|
||||
|
||||
- 计算正整数的阶乘。
|
||||
- 计算斐波那契数列。
|
||||
|
||||
实现递归表达式:
|
||||
|
||||
- 递归表达式(递归体)
|
||||
- 边界条件(递归出口)
|
||||
|
||||
```cpp
|
||||
int factorial(int n)
|
||||
{
|
||||
if (n == 0 || n == 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return n * factorial(n-1);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
递归调用时,函数调用栈可称为“递归工作栈”。
|
||||
|
||||
- 每进入一层递归,就将递归调用所需的信息压入栈顶。
|
||||
- 每退出一层递归,就从栈顶弹出相应信息。
|
||||
|
||||
```cpp
|
||||
int Fib(int n)
|
||||
{
|
||||
if (n == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (n == 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Fib(n-1) * Fib(n-2);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
递归缺点:
|
||||
|
||||
- 太多层递归可能会导致栈溢出。
|
||||
- 可能包含很多重复计算。
|
||||
|
||||
可以自定义栈,将递归算法改造成非递归算法。
|
||||
BIN
ch3/stack-applications/bracket-matching-flowchart.png
Normal file
BIN
ch3/stack-applications/bracket-matching-flowchart.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 519 KiB |
95
ch3/stack-applications/bracket_matching.cpp
Normal file
95
ch3/stack-applications/bracket_matching.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#define MaxSize 10
|
||||
typedef struct
|
||||
{
|
||||
char data[MaxSize];
|
||||
int top;
|
||||
} SqStack;
|
||||
|
||||
void InitStack(SqStack &S)
|
||||
{
|
||||
S.top = -1;
|
||||
}
|
||||
|
||||
bool StackEmpty(SqStack S)
|
||||
{
|
||||
return S.top == -1;
|
||||
}
|
||||
|
||||
bool StackFull(SqStack S)
|
||||
{
|
||||
return S.top == MaxSize - 1;
|
||||
}
|
||||
|
||||
bool Push(SqStack &S, char x)
|
||||
{
|
||||
if (StackFull(S))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
S.data[++S.top] = x;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Pop(SqStack &S, char &x)
|
||||
{
|
||||
if (StackEmpty(S))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
x = S.data[S.top--];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetTop(SqStack &S, char &x)
|
||||
{
|
||||
if (StackEmpty(S))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
x = S.data[S.top];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool bracketCheck(char str[], int length)
|
||||
{
|
||||
SqStack S;
|
||||
InitStack(S);
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (str[i] == '(' || str[i] == '[' || str[i] == '{')
|
||||
{
|
||||
Push(S, str[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (StackEmpty(S))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
char topElem;
|
||||
Pop(S, topElem);
|
||||
if (str[i] == ')' && topElem != '(')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (str[i] == ']' && topElem != '[')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (str[i] == '}' && topElem != '{')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return StackEmpty(S);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
char c[MaxSize + 1] = {"({([])}[])"};
|
||||
printf("%d\n", bracketCheck(c, MaxSize));
|
||||
return 0;
|
||||
}
|
||||
@@ -155,15 +155,17 @@ bool StackFull(ShStack S)
|
||||
|
||||
## 2. 链栈
|
||||
|
||||
### 2.1. 定义
|
||||
|
||||
```cpp
|
||||
typedef struct LNode
|
||||
{
|
||||
int data;
|
||||
ElemType data;
|
||||
struct LNode *next;
|
||||
} LNode, *LinkStack;
|
||||
```
|
||||
|
||||
### 2.1. 初始化
|
||||
### 2.2. 初始化
|
||||
|
||||
```cpp
|
||||
// 初始化一个链栈,带头结点
|
||||
@@ -187,7 +189,7 @@ bool StackEmpty(LinkStack S)
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2. 进栈
|
||||
### 2.3. 进栈
|
||||
|
||||
```cpp
|
||||
// 进栈
|
||||
@@ -201,7 +203,7 @@ bool Push(LinkStack &S, int x)
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3. 出栈
|
||||
### 2.4. 出栈
|
||||
|
||||
```cpp
|
||||
// 出栈
|
||||
@@ -219,7 +221,7 @@ bool Pop(LinkStack &S, int &x)
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4. 获取栈顶元素
|
||||
### 2.5. 获取栈顶元素
|
||||
|
||||
```cpp
|
||||
// 读取栈顶元素
|
||||
|
||||
Reference in New Issue
Block a user