栈是限定仅在表尾进行插入和删除操作的线性表。
队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。
栈的定义
栈(stack)是限定仅在表尾进行插入和删除操作的线性表。
我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出(Last In First Out)的线性表,简称 LIFO 结构。
栈的插入操作,叫做进栈,也称压栈、入栈。
栈的删除操作,叫做出栈,也有的叫弹栈。
进栈出栈的变化形式
元素数量多个,出栈次序会有很多种可能。
栈的抽象数据类型
1 |
ADT 栈 |
栈的顺序存储结构及实现
栈的顺序存储结构
栈的结构定义
1 |
typedef init SElemType; |
栈的顺序存储结构——进栈操作
进栈操作 push,其代码如下:
1 |
Status Push (SqStack *S, SElemType e) |
栈的顺序存储结构——出栈操作
出栈操作 pop,其代码如下:
1 |
Status Pop (SqStack *S, SElemType *e) |
进栈和出栈没有涉及到任何循环语句,因此时间复杂度均是 O(1)。
两栈共享空间
使用这样的数据结构,通常都是当两个栈的空间需求有相反关系时,也就是一个栈增长时另一个栈在缩短的情况。
栈的链式存储结构及实现
栈的链式存储结构
栈的链式存储结构,简称为链栈。
链栈的结构代码如下:
1 |
typedef struct StackNode |
栈的链式存储结构——进栈操作
1 |
/* 插入元素 e 为新的栈顶元素 */ |
栈的链式存储结构——出栈操作
假设变量 p 用来存储要删除的栈顶结点,将栈顶指针下移一位,最后释放 p 即可。
1 |
Status Pop (LinkStack *S, SElemType *e) |
链栈的进栈 push 和出栈 pop 操作没有任何循环操作,时间复杂度均为O(1)。
如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好是用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些。
栈的作用
栈的引入简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚焦于我们要解决的问题核心。
栈的应用——递归
斐波那切数列实现
1 |
int Fbi (int i) |
递归定义
在高级语言中,调用自己和其他函数并没有本质的不同。我们把一个直接调用自己或通过一系列的调用语句间接地调用自己的函数,称做递归函数。
每个递归定义必须至少有一个条件,满足递归不再进行,即不再引用自身而是返回值退出。
栈的应用——四则运算表达式求值
将中缀表达式转化为后缀表达式(栈用来进出运算的符号)。
将后缀表达式进行运算得出结果(栈用来进出运算的数字)。
队列的定义
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。
队列的抽象数据类型
1 |
ADT 队列(Queue) |
循环队列
循环队列定义
我们把队列的这种头尾相接的顺序存储结构称为循环队列。
队列的链式存储结构及实现
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。
链队列的结构为:
1 |
typedef int QElemType; |
队列的链式存储结构——入队操作
1 |
Status EnQueue(LinkQueue *Q, QElemType e) |
队列的链式存储结构——出队操作
1 |
Status DeQueue(LinkQueue *Q, QElemType *e) |
在可以确定队列长度最大值的情况下,建议用循环队列,如果你无法预估队列的长度时,则用链队列。