完成栈的应用

This commit is contained in:
lifei
2020-12-01 22:02:00 +08:00
parent 7be599dd81
commit 542406f555
10 changed files with 1198 additions and 8 deletions

View File

@@ -182,6 +182,7 @@ free(L.data);
**问题**
请描述顺序表和链表的 bla bla bla
实现线性表时,用顺序表还是链表好?
**答题思路**

View File

@@ -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 OUTFIFO
### 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. 问题
若数据元素输入序列为1234则哪些输出序列是合法的哪些是非法的
$$
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
View 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
View 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
View 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
View 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;
}

View File

@@ -0,0 +1,260 @@
# 栈的应用
## 1. 括号匹配
```cpp
({([])}[])
```
最后出现的左括号最先被匹配后进先出LIFO。每出现一个右括号就“消耗”出栈一个左括号。
- 遇到左括号就入栈。
- 遇到右括号,就出栈一个左括号。
![括号匹配算法流程图](bracket-matching-flowchart.png)
实现思路:
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 languageForth、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);
}
}
```
递归缺点:
- 太多层递归可能会导致栈溢出。
- 可能包含很多重复计算。
可以自定义栈,将递归算法改造成非递归算法。

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 KiB

View 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;
}

View File

@@ -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
// 读取栈顶元素