一、栈
1、基本概念
栈(Stack)是只允许在表尾进行插入和删除操作的线性表。对栈来说,表尾端称为栈顶(top),表头端称为栈底(Bottom)。不含元素的空表称为空栈。栈的示意图如下:
栈的修改是按后进先出的原则进行的,因此栈又称为后进先出(Last In First Out, LIFO)的线性表。
栈有如下的基本操作:
操作 | 说明 |
---|---|
init(&S) |
初始化一个空栈 |
isEmpty(S) |
判断一个栈是否为空,若为空返回true ,否则返回false |
push(&S, x) |
进栈 |
pop(&S, &x) |
出栈 |
getTop(S, &x) |
读栈顶元素 |
destroy(&S) |
销毁栈 |
2、存储结构
栈是一种操作受限的线性表,类似于线性表,它也有对应的两种存储方式。
(1)顺序栈
采用顺序存储的栈称为顺序栈。它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针指向当前栈顶元素的位置。用C语言可对顺序栈作如下定义:
1 |
|
下面对栈的一些基本操作进行实现:
- 初始化
1 | void init(SqStack &S) |
注:栈顶指针的初始值除了可以设置为 -1 外,还可以设置为 0,这相当于规定栈顶指针指向栈顶元素的下一个存储单元。
- 判断栈是否为空
1 | bool isEmpty(SqStack &S) |
- 进栈
1 | bool push(SqStack &S, ElemType x) |
注:如果初始化时 top 为 0,则进栈时应先入栈,再将 top 加1。
- 出栈
1 | bool pop(SqStack &S, ElemType &x) |
注:如果初始化时 top 为 0,则 top 需要先减1再进行出栈操作。
- 读栈顶元素
1 | bool getTop(SqStack S, ElemType &x) |
注:如果初始化时 top 为 0,则读取 top -1 位置的数据元素。
(2)链栈
采用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高效率,且不存在栈满上溢的情况,通常采用单链表实现。用C语言可对链栈作如下定义:
1 | typedef struct LinkNode |
链栈的操作于链表相似,这里不再赘述。
二、队列
1、基本概念
队列(Queue)是一种操作受限的线性表,只允许在表的一端进行插入,而在另一端进行删除。向队列中插入元素称为入队或进队,删除元素称为出队或离队。队列的示意图如下:
队列是一种先进先出(First In First Out, FIFO)的线性表。
下面给出队列的一些基本操作:
操作 | 说明 |
---|---|
InitQueue |
初始化队列 |
QueueEmpty |
判断队列是否为空,若队列为空返回true ,否则返回false |
EnQueue |
入队 |
DeQueue |
出队 |
GetHead |
读队头元素 |
2、存储结构
(1)顺序存储
队列的顺序存储是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:队头指针 front 指向队头元素,队尾指针 rear 指向队尾元素的下一个位置(也可以指向队尾元素)。用C语言可对队列作如下定义:
1 |
|
为了在C语言中描述方便起见,在此约定:初始化创建空队列时,令 front = rear = 0,每当插入新的队列尾元素时,尾指针 rear 加 1;每当删除队列头元素时,头指针 front 加 1。因此,在非空队列中,头指针始终指向队列头元素,而尾指针始终指向队列尾元素的下一个位置,如下图所示。
假设当前队列分配的最大空间为 6,则当队列处于上图 (d) 所示的状态时不可再继续插入新的队尾元素,否则会出现溢出现象,即因数组越界而导致程序的非法操作错误。事实上,此时队列的实际可用空间并未占满,所以这种现象称为“假溢出”。这是由“队尾人队,队头出队”这种受限制的操作造成的。怎样解决这种“假溢出”问题呢? 一个较巧妙的办法是将顺序队列变为一个环状的空间,如下图所示,称之为循环队列。
头、尾指针以及队列元素之间的关系不变,只是在循环队列中,头、尾指针“依环状增 1” 的操作可用“模”运算来实现。通过取模,头指针和尾指针就可以在顺序表空间内以头尾衔接的方式“循环”移动。
- 初始时:
Q.front = Q.rear = 0
- 入队:
Q.rear = (Q.rear + 1) % MAXSIZE
- 出队:
Q.front = (Q.front + 1) % MAXSIZE
- 队列长度:
(Q.rear + MAXSIZE - Q.front) % MAXSIZE
由上图可知,队空和队满时都有 Qfront == Q.rear
,那么该如何判断队列是否为空呢?下面介绍几种常见的解决方法:
- 牺牲一个单元来区分队空和队满,入队时少用一个队列单元,以队头指针在队尾指针的下一位置作为队满的标志,如上图 (d) 所示。
- 类型中增设表示元素个数的数据成员。
- 类型中增设 tag 数据成员,以区分是队满还是队空。
下面对队列的一些基本操作进行实现:
- 初始化
1 | void InitQueue(SqQueue &Q) |
- 判断队列是否为空
1 | bool isEmpty(SqQueue Q) |
- 入队
1 | bool EnQueue(SqQueue &Q, ElemType x) |
- 出队
1 | bool DeQueue(SqQueue &Q, ElemType &x) |
(2)链式存储
队列的链式表示称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表。头指针指向队头结点,尾指针指向队尾结点,即单链表的最后一个结点。用C语言可对链队作如下定义:
1 | typedef struct |
链式队列的基本操作本质上与顺序队列相同,只是具体实现不同,这里不再进行赘述。