1. 栈的定义
栈是只允许在一端进行插入或删除操作的线性表。
逻辑结构:与普通线性表相同。 数据运算:插入、删除操作有区别。
1.1 重要术语:栈顶、栈底、空栈
栈顶:允许插入和删除的一端。 栈底:不允许插入和删除的一端。
特点:后进先出。LIFO Last In First Out
1.2 栈要实现的基本操作
- 初始化栈(创)
- 销毁栈(销)
- 进栈:若栈s未满,则将x加入使之成为新的栈顶。(增)
- 出栈:若栈s非空,则弹出栈顶元素,并用x返回。(删)
- 读栈顶元素:若栈s非空,则用x返回栈顶元素。(查)
- 判断栈是否为空。
个不同元素进栈,出栈元素不同排列的个数为 。该公式称为卡特兰数(Catalan)
2. 顺序栈:用顺序存储的方式实现的栈
基本操作:创、销、删、查、判空、判满
2.1 顺序栈的基本操作
2.1.1 顺序栈基本操作:初始化操作、判空
2.1.2 顺序栈基本操作:进栈
2.1.3 顺序栈基本操作:出栈
2.1.4 顺序栈基本操作:读栈顶元素
跟出栈区别仅有不改变top游标指针
top初始化也可以设为 0 :
则判空和判满的条件改变,判空为 s.top = 0; 判满为 s.top = MAXSIZE
进栈和出栈代码也发生相应变化:
进栈
s.data[s.top++] = x;
出栈
x=s.data[--s.top];
2.2 共享栈
顺序栈容量不可以改变,可以通过链式存储或设置较大内存空间解决,为避免初始设置较大内存空间的浪费,可以设置共享栈。
3. 链栈:用链式存储实现的栈
本质是只能一端进行插入和删除的单链表。
推荐使用不带头结点的链表实现。
3.1 示例:
3.1 定义链栈的存储结构
typedef int Elem;
typedef struct Snode{
Elem data; //数据类型,这样定义容易修改
Snode *next;
}Snode, *LinkList;
3.2 初始化
void initStack(LinkList &s){ // 初始化
s = NULL;
return;
}
3.3 判断栈空
bool isEmpty(LinkList &s)
{
return (s == NULL);
}
3.4 入栈
void push(LinkList &s){ // 入栈
Elem e;
Snode *p;
printf("入栈的值:\n");
scanf("%d",&e);
p = new Snode;
p->data = e;
p->next = s;
s = p;
return;
}
3.5 出栈
void pop(LinkList &s){ // 出栈
Elem e;
if(isEmpty(s)){
printf("栈空\n");
return;
}
e = s->data;
LinkList p = s;
s = s->next;
delete p;
printf("%d 出栈成功\n",e);
return;
}
3.6 获取栈顶元素并返回
Elem getTop(LinkList &s)
{
if(!isEmpty(s))
{
printf("%d\n",s->data);
return s->data;
}
}
3.7 遍历栈
void show(LinkList &s){ // 显示栈
if(!isEmpty(s)){
LinkList p = s;
while(p != NULL){
printf("%d ",p->data);
p = p->next;
}
printf("\n");
return;
}
}
3.8 清空栈
void toEmpty(LinkList &s){ // 清空
LinkList p;
while(s != NULL){
p = s;
s = s->next;
delete p;
}
return;
}
4. 队列
只允许在一端插入,在另一端删除的线性表。
4.1 重要术语:队头、队尾、空队列
队头:允许删除的一端。
队尾:允许插入的一端。
特点:先进先出。FIFO First In First Out。
4.2 基本操作
- 初始化队列,构造一个空队列。
- 销毁队列,销毁并释放队列Q所占用的内存空间。
- 入队,若队列Q未满,将x加入,使之成为新的队尾。
- 出队,若队列Q非空,删除队头元素,并用x返回。
- 读队头元素,若队列Q非空,则将队头元素赋值给x。
5. 顺序存储实现队列
可以通过静态数组实现队列的数据元素,设置两个标记队列的队头和队尾。 定义为:
# define MAXSIZE 10
typedef struct
{
int data[MAXSIZE];
int front, rear; //这种情况下为了区分判空和判满需要浪费一个存储空间
//int size; 可以用来判空判满的另一种辅助方式
//int tag; 可以用来判空判满的另一种辅助方式,插入时tag置为1,删除时为0
}SqQueue;
5.1 顺序队列的基本操作:初始化和判空操作
5.2 顺序队列的基本操作:判满操作
为了避免浪费存储空间,顺序队列通常采用循环结构,即循环队列。其形式如下:
在此情况下采用上述的数据结构定义方式,会导致判断队列空满为一个形式,因此此处采用:
//判断队满,循环队列情况下,在此种定义方式中判断队满需要浪费一个存储单元
bool isFull(SqQueue q)
{
return ((q.rear + 1) % MAXSIZE == q.front);
}
也可以采用数据结构定义中增加记录队列状态的参数。
5.2 顺序队列的基本操作:入队(只能从队尾插入)
5.3 顺序队列的基本操作:出队(只能从队头出队)
//出队
bool OutQueue(SqQueue& q, int x)
{
if (isEmpty(q)) //判断队空
return false;
x = q.data[q.front];
q.front = (q.front + 1) % MAXSIZE; //队头指针后移
return true;
}
5.4 顺序队列的基本操作:获取队头元素的值
//获取队头元素的值
bool OutQueue(SqQueue& q, int x)
{
if (isEmpty(q)) //判断队空
return false;
x = q.data[q.front];
//跟出队操作的区别就是没有队头指针后移
return true;
}
注意:队尾指针可能指向队尾元素,而非队尾元素的后一个位置(下一个应该插入的位置),那么代码会有区别。
判空:
(q.rear+1)% MAXSIZE = q.front
判满:
牺牲一个存储单元或增加一个辅助变量
6. 链队列:链式存储实现队列
6.1 链式存储队列的定义
6.2 链式存储队列的基本操作:初始化和判空
1) 带头结点
2) 不带头结点
6.3 链式存储队列的基本操作:入队
1) 带头结点
2) 不带头结点
注意第一个元素入队的处理。
6.4 链式存储队列的基本操作:出队
1) 带头结点
注意最后一个元素出队的处理。
2) 不带头结点
注意最后一个元素出队的处理。
链式存储一般不会队满,除非内存不足。
7. 双端队列
只允许从两端插入、两端删除的线性表。 同时还有输入受限的双端队列和输出受限的双端队列。 若数据元素输入序列为 ,则输出序列有 种。
8. 栈的应用
8.1 括号匹配
利用栈的先进后出特性,遇到左括号就入栈,遇到右括号就将左括号出栈。
8.2 表达式求值
8.2.1 中缀表达式
中缀表达式由操作符、运算符、界限符三部分组成,中缀表达式中界限符是必不可少的。
8.2.2 后缀表达式(逆波兰表达式)
中缀转后缀的方法,机算转换遵循左优先原则,可保证运算顺序唯一,遵循左优先原则。
后缀表达式的手算方法,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算,合体为一个操作数。应注意两个操作数的左右顺序。
1)中缀表达式转后缀表达式(机算)
2) 用栈实现后缀表达式计算
-
- 从左往右扫描下一个元素,直到处理完所有元素
-
- 若扫描到操作数则压入栈,并回到1,否则执行3
-
- 若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到1
- 注意:先出栈的是右操作数
3)用栈实现中缀表达式的求值
相当于中缀转后缀表达式和后缀表达式计算的结合。
-
- 初始化两个栈,操作数栈和运算符栈。
-
- 若扫描到操作数,压入操作数栈
-
- 若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑亚茹运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
8.2.3 前缀表达式(波兰表达式)
中缀转前缀的方法,机算转换遵循右优先原则,只要右边的运算符能先计算,就优先算右边的。
1)用栈实现前缀表达式计算
-
- 从右往左扫描下一个元素,直到处理完所有元素
-
- 若扫描到操作数则压入栈,并回到1,否则执行3
-
- 若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到1
- 注意:先出栈的是左操作数
8.3 栈用:递归
函数调用特点:最后被调用的函数最先执行结束(LIFO)
8.3.1 函数调用背后过程
8.3.2 栈在递归中的应用
1)递归缺点:
效率低,太多层递归可能会导致栈溢出,可能包含很多重复计算
解决方法可以尝试: 自定义栈将递归算法改造成非递归算法?
9. 队列的应用
9.1 队列应用:树的层次遍历
每处理一个结点,将该结点的左右孩子放在队尾,处理完的结点在头部出队。
9.2 队列应用:图的广度优先遍历
9.3 队列在操作系统中的应用
多个进程争抢着使用有限的系统资源时,FCFS(First Come First Service 先来先服务)是一种常用策略。
EG:打印数据缓冲区:用队列组织打印数据