队列
队列是线性表的一种,同栈一样,它也属于 操作受限的线性表。
1. 队列的定义和特点
队列只允许在表的一端插入元素,另一端删除元素。
允许插入的一端被称为队尾;允许删除的一端被称为对头。
下图为入队列、出队列示意图:
a 1 a_1 a1为对头元素, a n a_n an为队尾元素。栈中元素按 a 1 , a 2 , ⋯ , a n a_1, a_2, \cdots , a_n a1,a2,⋯,an的次序入对栈, 出队也只能按照这个顺序进行。
队列的修改规则:先进先出(First In First Out, FIFO)
2. 循环队列
2.1 存储结构表示
循环队列是利用一组地址连续的存储单元依次存放从对头到队尾的数据元素。
附设front
指针来指示队列头元素;rear
指针指示队列尾元素。
队列的顺序存储结构如下:
#define MAXQSIZE 100 //队列可能达到的最大长度
typedef struct
{
QElemType *base; //存储空间的基地址
int front; //头指针 -> 更像是偏移量
int rear; //尾指针 -> 更像是偏移量
}SqQueue;
初始化创建空队列时,令front= rear = 0
,
每当插入新的队列尾元素时,尾指针rear
增1;每当删除队列头元素时,头指针front
增1。
队列的假溢出:假设当前队列分配的最大空间为4,则当队列处于下图所示的状态时,不可再继续插入新的队尾元素,否则会出现溢出现象, 即因数组越界而导致程序的非法操作错误。而此时队列的实际可用空间并未占满。
将顺序队列变为一个循环队列即可解决这类问题。
下图中,对头元素的
a
3
a_3
a3,入队前队尾指针Q.rear
是3;当下一个元素
a
4
a_4
a4入队后,队尾指针Q.rear
回到了0
Q.rear = (Q.rear + 1) % 4
循环队列为空状态:
此时Q.front == Q.rear
下图为队满的状态,共有两种:
上面一种队列空间被占满,但是此时**Q.front == Q.rear
**,与队空的判断条件一致,判断条件冲突了。
当Q.front == Q.rear
时,无法区分队列是空还是满的状态。
因此通常使用下面一种:少用一个元素空间,此时(Q.rear+1)%MAXSIZE == Q.front
循环队列队空和对满的条件:
- 队空的条件:
Q.front == Q.rear
- 队满的条件:
(Q.rear+1)%MAXSIZE == Q.front
2.2 初始化
循环队列的初始化操作就是动态分配一个预定义大小为MAXSIZE
的数组空间。
算法步骤:
- 为队列分配一个最大容量为
MAXSIZE
的数组空间,base
指向数组空间的首地址。 - 头指针
front
和尾指针rear
置为零, 表示队列为空。
Status InitQueue(SqQueue *Q)
{
Q->base = (QElemType*)malloc(sizeof(QElemType)*MAXQSIZE); //分配容量为MAXSIZE的数组
if (Q->base==NULL) exit(OVERFLOW); //存储分配失败
Q->front = Q->rear = 0; //头指针和尾指针设置为零,队列为空
return OK;
}
2.3 求队列长度
对于非循环队列,尾指针和头指针的差值便是队列长度。
对于循环队列,差值可能为负数,所以需要将差值加上MAXSIZE
, 然后与MAXSIZE
求余。
Status QueueLength(const SqQueue *Q)
{
return (Q->rear-Q->front+MAXQSIZE)%MAXQSIZE;
}
2.4 入队
入队操作就是在队尾插入一个新元素。
算法步骤:
- 判断队列是否满,若满则返回
ERROR
- 将新元素插入队尾
- 队尾指针加1
Status EnQueue(SqQueue *Q, QElemType e)
{
if ((Q->rear+1)%MAXQSIZE == Q->front) return ERROR; //判断队列是否满
Q->base[Q->rear] = e; //新元素插入队尾
Q->rear = (Q->rear+1)%MAXQSIZE; //队尾指针加1
return OK;
}
2.5 出队
出队操作就将对头元素删除。
算法步骤:
- 判断队列是否空,若空则返回
ERROR
- 保存对头元素
- 队头指针加1
Status DeQueue(SqQueue *Q, QElemType *e)
{
if (Q->rear == Q->front) return ERROR; //判断队列是否空
*e = Q->base[Q->front]; //保存对头元素
Q->front = (Q->front+1)%MAXQSIZE;; //队头指针加1
return OK;
}
2.6 取得循环队列的队头元素
当队列为非空时,返回当前对头元素的值,对头指针不变。
QElemType GetHead(SqQueue *Q)
{
if (Q->rear != Q->front) return Q->base[Q->front];
}
2.7 案例
给出一个简单的案例,将数据元素类型QElemType
设置为int
,所以这是一个数据元素为int
的循环队列。
包含上述4种操作,便于理解、应用。
下述代码运行环境:VS2019,其他环境可移植。
#include <stdio.h>
#include <malloc.h>
typedef struct
{
int* base; //存储空间的基地址
int front; //头指针 -> 更像是偏移量
int rear; //尾指针 -> 更像是偏移量
}SqQueue;
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;
#define MAXQSIZE 100 //队列可能达到的最大长度
// 初始化循环队列
// 函数参数: Q -> 操作台地址
// 返回值:1代表运行正常
Status InitQueue(SqQueue* Q)
{
Q->base = (int*)malloc(sizeof(int) * MAXQSIZE); //分配一个容量为MAXSIZE的数组
if (Q->base == NULL) exit(OVERFLOW); //存储分配失败
Q->front = Q->rear = 0; //头指针和尾指针设置为零,队列为空
return OK;
}
// 求队列长度
// 函数参数: Q -> 操作台地址
// 返回值:1代表运行正常
Status QueueLength(const SqQueue* Q)
{
return (Q->rear - Q->front + MAXQSIZE) % MAXQSIZE;
}
// 入队
// 函数参数: Q -> 操作台地址; e -> 入队参数
// 返回值:1代表运行正常; 0代表运行有问题
Status EnQueue(SqQueue* Q, int e)
{
if ((Q->rear + 1) % MAXQSIZE == Q->front) return ERROR; //判断队列是否满
Q->base[Q->rear] = e; //新元素插入队尾
Q->rear = (Q->rear + 1) % MAXQSIZE; //队尾指针加1
return OK;
}
// 出队
// 函数参数: Q -> 操作台地址; e -> 存放出队元素
// 返回值:1代表运行正常; 0代表运行有问题
Status DeQueue(SqQueue* Q, int* e)
{
if (Q->rear == Q->front) return ERROR; //判断队列是否空
*e = Q->base[Q->front]; //保存对头元素
Q->front = (Q->front + 1) % MAXQSIZE;; //队头指针加1
return OK;
}
// 取得队头元素
// 函数参数: Q -> 操作台地址
// 返回值:0代表运行有问题
int GetHead(SqQueue* Q)
{
if (Q->rear == Q->front) return ERROR; //判断队列是否空
return Q->base[Q->front];
}
int main()
{
SqQueue queue; //创建队列的操作台
int elem = 0; //存放出栈元素
InitQueue(&queue); //初始化队列
EnQueue(&queue, 6); //将数据6入队
EnQueue(&queue, 89); //将数据89入队
EnQueue(&queue, 101); //将数据101入队
printf("The length of queue is : %d\n", QueueLength(&queue)); //打印队列长度
printf("%d\n", GetHead(&queue)); //打印队头元素
DeQueue(&queue, &elem); //出队
printf("elem: %d\n", elem); //打印出队元素
printf("The length of queue is : %d\n", QueueLength(&queue)); //打印队列长度
printf("%d\n", GetHead(&queue)); //打印队头元素
DeQueue(&queue, &elem); //出队
printf("elem: %d\n", elem); //打印出队元素
printf("The length of queue is : %d\n", QueueLength(&queue)); //打印队列长度
printf("%d\n", GetHead(&queue)); //打印队头元素
return 0;
}
3. 链队
3.1 链队的结构表示
链队是指采用链式存储结构实现的队列。通常链队用单链表来表示。
如上图所示,一个链队需要两个指示队头和队尾的指针(分别称为头指针和尾指针)才能唯一确定。
这里和单链表一样, 为了操作方便起见,给链队添加一个头结点, 并令头指针始终指向头结点。
typedef struct QNode
{
QElemType data; //数据
struct QNode *next; //指针
}QNode, *QueuePtr;
typedef struct
{
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
3.2 初始化
链队的初始化操作就是构造一个只有一个头结点的空队。
算法步骤:
- 生成新的头结点作为头结点,队尾和对头指针都指向该结点
- 将头结点的指针域置空
Status InitQueue(LinkQueue *Q)
{
Q->front = Q->rear = (QNode*)malloc(sizeof(QNode)); //生成新结点
Q->front->next = NULL; //将头结点的指针域置空
return OK;
}
3.3 入队
链队在入队前不需要判断队是否满,需要为入队元素动态分配一个结点空间。
算法步骤:
- 为入队元素分配结点空间,用指针p指向
- 将新结点数据域置为e
- 将新结点插入到队尾
- 修改队尾指针为p
Status EnQueue(LinkQueue *Q, QElemType e)
{
QNode* p = (QNode*)malloc(sizeof(QNode)); //1. 为入队元素分配结点空间
p->data = e; //2. 将新结点数据域置为e
p->next = NULL;
Q->rear->next = p; //3. 将新结点插入到队尾
Q->rear = p; //4. 修改队尾指针为p
return OK;
}
3.4 出队
链队在出队前需要判断队列是否为空,同时链队在出队后需要释放出队头元素的所占空间。
- 判断队列是否为空,若空则返回ERROR。
- 临时保存队头元素的空间,以备释放。
- 修改队头指针,指向下一个结点。
- 判断出队元素是否为最后一个元素,若是,则将队尾指针重新赋值, 指向头结点。
- 释放原队头元素的空间。
Status DeQueue(LinkQueue* Q, QElemType* e)
{
if (Q->rear == Q->front) return ERROR; //1. 判断队列是否空
QNode* p = Q->front->next; //2. 临时保存队头元素的空间,以备释放
*e = p->data;
Q->front->next = p->next; //3. 修改队头指针,指向下一个结点
if (Q->rear == p) Q->rear = p->next; //4. 判断出队元素是否为最后一个元素
free(p); //5. 释放原队头元素的空间
return OK;
}
3.5 取得队头元素
当队列非空时,此操作返回当前队头元素的值,队头指针保持不变。
QElemType GetHead(LinkQueue *Q)
{
if (Q->front != Q->rear) return Q->front->next->data;
}
3.6 案例
给出一个简单的案例,将数据元素类型QElemType
设置为int
,所以这是一个数据元素为int
的链队。
包含上述4种操作,便于理解、应用。
下述代码运行环境:VS2019,其他环境可移植。
#include <stdio.h>
#include <malloc.h>
typedef struct QNode
{
int data; //数据
struct QNode* next; //指针
}QNode, * QueuePtr;
typedef struct
{
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;
#define MAXQSIZE 100 //队列可能达到的最大长度
// 初始化顺序栈
// 函数参数: Q -> 指针地址
// 返回值:1代表运行正常
Status InitQueue(LinkQueue* Q)
{
Q->front = Q->rear = (QNode*)malloc(sizeof(QNode)); //生成新结点
Q->front->next = NULL; //将头结点的指针域置空
return OK;
}
// 入队
// 函数参数: Q -> 指针地址; e -> 入队参数
// 返回值:1代表运行正常; 0代表运行有问题
Status EnQueue(LinkQueue* Q, int e)
{
QNode* p = (QNode*)malloc(sizeof(QNode)); //1. 为入队元素分配结点空间
p->data = e; //2. 将新结点数据域置为e
p->next = NULL;
Q->rear->next = p; //3. 将新结点插入到队尾
Q->rear = p; //4. 修改队尾指针为p
return OK;
}
// 出队
// 函数参数: Q -> 指针地址; e -> 出队地址
// 返回值:1代表运行正常; 0代表运行有问题
Status DeQueue(LinkQueue* Q, int* e)
{
if (Q->rear == Q->front) return ERROR; //1. 判断队列是否空
QNode* p = Q->front->next; //2. 临时保存队头元素的空间,以备释放
*e = p->data;
Q->front->next = p->next; //3. 修改队头指针,指向下一个结点
if (Q->rear == p) Q->rear = p->next; //4. 判断出队元素是否为最后一个元素
free(p); //5. 释放原队头元素的空间
return OK;
}
// 取得队头元素
// 函数参数: Q -> 指针地址; e -> 出队地址
// 返回值:队头元素
int GetHead(LinkQueue* Q)
{
if (Q->front != Q->rear) return Q->front->next->data;
}
int main()
{
LinkQueue queue; //创建指向队列的指针
int elem = 0; //存放出栈元素
InitQueue(&queue); //初始化队列
EnQueue(&queue, 6); //将数据6入队
EnQueue(&queue, 89); //将数据89入队
EnQueue(&queue, 101); //将数据101入队
printf("%d\n", GetHead(&queue)); //打印队头元素
DeQueue(&queue, &elem); //出队
printf("elem: %d\n", elem); //打印出队元素
printf("%d\n", GetHead(&queue)); //打印队头元素
DeQueue(&queue, &elem); //出队
printf("elem: %d\n", elem); //打印出队元素
printf("%d\n", GetHead(&queue)); //打印队头元素
return 0;
}