数据结构(六)——顺序队列、链队列

队列——顺序队列、链队列

队列概念

​ 队列是一种特殊的、只允许在一端进行数据的输入,在另一端进行数据的删除、具有先进先出(FIFO)特性的线性表,进行插入操作的一端称为队尾,进行删除操作的一端称为队头

顺序队列

​ 顺序队列就是限制只能在队列两端分别进行插入和删除操作的线性表

顺序队列定义

​ 顺序队列的定义就是在顺序表的基础上加上代表队头 front 和队尾 rear 的头尾”指针“,用来对队列的其他操作服务

1
2
3
4
5
6
7
#define MAXSIZE 10

typedef struct {
int data[MAXSIZE];
int front, rear;
int QueueSize;
}SeQueue;

初始化队列

​ 所谓初始化就是一个分配资源,再让变量值都合法并符合最初时的状态,队列的初始状态和栈很像,需要将队头 front 和队尾 rear 都为 -1,并让队列的长度归 0

1
2
3
4
5
6
7
SeQueue* InitQueue() {
SeQueue* sq;
sq = (SeQueue*)malloc(sizeof(SeQueue));
sq->front = sq->rear = -1;
sq->QueueSize = 0;
return sq;
}

判空

​ 既然已经定义了队列的大小这一属性 QueueSize,就直接检测其值来判断队列是否为空即可

1
2
3
4
5
6
int EmptyQueue(SeQueue* sq) {
if (sq->QueueSize == 0)
return 1;
else
return 0;
}

入队

​ 入队操作是在队尾 rear 的后一个位置上向队内插入新的数据元素,所以需要先检验队列是否已满,而后为该位置赋值,再改变相应属性的值即可

1
2
3
4
5
6
7
8
9
10
int InQueue(SeQueue* sq, int d) {
if (sq->QueueSize == MAXSIZE)
return -1;
else {
sq->rear = (sq->rear + 1) % MAXSIZE;
sq->data[sq->rear] = d;
sq->QueueSize++;
return 1;
}
}

出队

​ 队列的出队有两种方法,一种是直接移动队头 front 指针,这样的出队方法十分简单快速,但缺点就是这样的出队方式会造成队列实际长度的减少,从而造成假溢出(顺序队列因多次入队列和出队列操作后出现的尚有存储空间但不能进行入队列操作的溢出)

1
2
3
4
5
6
7
8
9
10
int OutQueue1(SeQueue* sq, int d) {
if (sq->QueueSize == 0)
return -1;
else {
sq->front = (sq->front + 1) % MAXSIZE;
d = sq->data[sq->front];
sq->QueueSize--;
return 1;
}
}

​ 第二种方法是不改变队头 front 的位置,在删除掉队头元素后,将整个队列中的所有元素都前移,再将队尾 rear 前移,这样做虽然不存在假溢出的问题,但是操作时间明显增加,每次操作都需要移动大量的元素

1
2
3
4
5
6
7
8
9
10
11
12
int OutQueue2(SeQueue* sq, int d) {
if (sq->QueueSize == 0)
return -1;
else {
d = sq->data[sq->front];
for (int i = 0; i < sq->QueueSize - 1; i++)
sq->data[i] = sq->data[i + 1];
sq->rear--;
sq->QueueSize--;
return 1;
}
}

链队

​ 链队同样是一种先进先出(FIFO)表,只不过使用了链式存储结构,在普通的顺序队列中增加了 front、rear 来表示队头和队尾指针

链队定义

​ 链队的定义需要使用两个结构体,QNode 用来表示链队中的每一个单个节点,LinkQueue 用来表示整个队列,其中需要定义链队特有的队头队尾指针

1
2
3
4
5
6
7
8
typedef struct QNode {
int data;
QNode* next;
}QNode;

typedef struct LinkQueue {
QNode *front, *rear;
}LinkQueue;

初始化链队

​ 与链表一样,链队的定义就是请求内存,并将队头、队尾指针置空的过程,最后返回这个队列

1
2
3
4
5
LinkQueue* InitLQueue() {
LinkQueue* Q = (LinkQueue*)malloc(sizeof(LinkQueue));
Q->front = Q->rear = NULL;
return Q;
}

链队判空

​ 如果链队的队头指针以及队尾指针均为空或其中之一为空即可判定该链队为空

1
2
3
4
5
6
int IsEmpty(LinkQueue* q) {
if (q->front == q->rear && q->front == NULL)
return 1;
else
return 0;
}

销毁链队

​ 销毁链队需要使用循环,遍历整个链队,一个节点一个节点的释放资源,最后释放掉整个队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void DestroyQueue(LinkQueue *q) {
QNode* r;
QNode* p = q->front;
if (p != NULL) {
r = p->next;
while (r != NULL) {
delete p;
p = r;
r = p->next;
}
}
delete p;
delete q;
}

入队

​ 链队的入队,先完成要入对的节点的定义和赋值,之后入队分成原队列是否为空两种情况。若原队列为空,将队头和队尾指针都指向该节点即可;若原队列为非空,设定队尾为加入数据的一端,连接队头和原队尾节点即完成入队

1
2
3
4
5
6
7
8
9
10
11
12
void InQueue(LinkQueue *q, int d) {
QNode* r;
r = (QNode*)malloc(sizeof(QNode));
r->data = d;
r->next = NULL;
if (q->rear == NULL)
q->front = q->rear = r;
else {
q->rear->next = r;
q->rear = r;
}
}

出队

​ 出队先判断队列是否本就为空,再检测是否只含有一个节点,是的话就将队头队尾指针置空,最后是普通情况,因为队尾是插入段,所以队头为删除端,只需要将队头指针后移即可,随后释放 r 节点的资源

1
2
3
4
5
6
7
8
9
10
11
void OutQueue(LinkQueue* q) {
QNode* r;
if (q->rear == NULL)
return;
r = q->front;
if (q->front == q->rear)
q->front = q->rear = NULL;
else
q->front = q->front->next;
delete r;
}

总结

​ (1)链队相比顺序队列,元素出队时不需要大量的移动现有节点,移动队头指针即可

​ (2)链队相比需要增加一些额外的存储空间

-------------本文结束感谢您的阅读-------------