采用链式存储的队列称为链队。链队是一个同时带有队头指针和队尾指针的单链表。其中队头指针始终指向队头结点,队尾指针始终指向队尾结点(即单链表的最后一个结点)。
注:链式队列和顺序队列的队尾指针指向不同,在顺序队列中队尾指针指向队尾元素的下一个位置,在链式队列中队尾指针指向队尾结点。
链队中元素结点结构体定义:
/**
* 链队列中的结点结构体定义
*/
typedef struct QNode {
/**
* 结点数据域,存储链队列中结点的数据
*/
int data;
/**
* 结点指针域,存储当前结点的后继结点的地址
*/
struct QNode *next;
} QNode;
链队结构体定义:
/**
* 链队列结构体定义
*/
typedef struct {
/**
* 存储链队列的队头结点的地址,即指向队头结点
*/
QNode *front;
/**
* 存储链队列的队尾结点的地址,即指向队尾结点
*/
QNode *rear;
} LinkedQueue;
queue->rear==NULL
或者 queue->front==NULL
时队空。front
始终指向队头结点;队尾指针 rear
始终指向队尾结点。rear
指向这个新结点。如果原队列为空,那么让队头指针 front
也指向该结点。front
所指向的结点元素,将其从链表中摘除,并让队头指针 front
指向其后继结点。如果该结点是最后一个结点,则将队头指针 front
和队尾指针 rear
都指向 NULL
。注,完整代码请参考:
- LinkedQueue.c
- LinkedQueue.java
- LinkedQueueTest.java
链队的常见操作如下:
void init(LinkedQueue **queue)
:初始化链队。其中 queue
表示链队。int isEmpty(LinkedQueue *queue)
:判断链队是否为空。其中 queue
表示链队。如果链队为空则返回 1,否则返回 0 表示非空。int enQueue(LinkedQueue **queue, int ele)
:将元素进队。其中 queue
表示链队;ele
表示待插入的新元素值。如果链队为满则不能入队,则返回 0;如果入队成功则返回 1 表示成功。int deQueue(LinkedQueue **queue, int *ele)
:将元素出队。其中 queue
表示链队。ele
用来存放出队的元素。如果链队为空则不能出队,则返回 0;如果出队成功则返回 1 表示成功。int size(LinkedQueue *queue)
:获取链队的长度。其中 queue
表示链队。返回链队中元素个数。int getFront(LinkedQueue *queue, int *ele)
:获取链队队头元素。其中 queue
表示链队;ele
用来存放队头元素。如果链队为空则无法获取队头元素则返回 0,否则返回 1。int getRear(LinkedQueue *queue, int *ele)
:获取链队队尾元素。其中 queue
表示链队;ele
用来存放队尾元素。如果链队为空则无法获取队尾元素则返回 0,否则返回 1。void print(LinkedQueue *queue)
:打印链队中所有元素。其中 queue
表示链队。void clear(LinkedQueue **queue)
:清空链队所有元素。其中 queue
表示链队。void destroy(LinkedQueue **queue)
:销毁链队。其中 queue
表示链队。init
初始化链队。
实现步骤:
front
指针和 rear
指针都指向 NULL
,表示是空队列。实现代码如下:
/**
* 初始化链队列
* @param queue 未初始化的链队列
*/
void init(LinkedQueue **queue) {
// 其实 queue 就相当于链队列的头结点,不过它有两个指针,分别存储队头结点和队尾结点的地址
// 为链队列头结点分配存储空间
*queue = (LinkedQueue *) malloc(sizeof(LinkedQueue));
// 将队头指针和队尾指针都指向 NULL,表示空队列
(*queue)->front = NULL;
(*queue)->rear = NULL;
}
isEmpty
判断链队是否为空,如果为空则返回 1,否则返回 0 表示非空。
实现步骤:
front
或者队尾指针 rear
是否指向 NULL,如果是则表示空队列则返回 1,否则如果不是空队列则返回 0。实现代码如下:
/**
* 判断链队列是否为空
* @param queue 链队列
* @return 如果链队列为空则返回 1,否则返回 0 表示非空
*/
int isEmpty(LinkedQueue *queue) {
// 只要链队列的队头指针或者队尾指针指向 NULL 则表示是空队
if (queue->front == NULL || queue->rear == NULL) {
return 1;
} else {
return 0;
}
}
enQueue
将元素入队。
实现步骤:
ele
,指定指针域初始指向 null
,表示这是一个新结点。front
和队尾指针 rear
都指向新结点。rear
指向新结点。实现代码如下:
/**
* 将元素入队
* @param queue 链队列
* @param ele 待入队的元素
*/
void enQueue(LinkedQueue **queue, int ele) {
// 0.因为是链表来表示队列,理论上不存在队满的情况,所以不需要校验队满
// 1.创建新结点
// 1.1 为新结点分配空间
QNode *newNode = (QNode *) malloc(sizeof(QNode));
// 1.2 为新结点指定数据域
newNode->data = ele;
// 1.3 为新结点指定数据域,初始指向 NULL
newNode->next = NULL;
// 2.将新结点入队
// 2.1 若队列为空,则新结点是队头结点,也是队尾结点
if ((*queue)->rear == NULL) {
// 因为链队列只有一个结点,所以将队头指针和队尾指针都指向新结点
(*queue)->front = newNode;
(*queue)->rear = newNode;
}
// 2.2 如果队列非空,则将新结点插入到队尾结点的后面,并将队尾指针指向新结点
else {
// 局部变量,记录队尾结点
QNode *tailNode = (*queue)->rear;
// 2.2.1 将新结点插入到队尾结点的后面
tailNode->next = newNode;
// 2.2.2 将队尾指针指向新结点
(*queue)->rear = newNode;
}
}
deQueue
将元素出队,即从链队的头部出队。如果不是空队列才能出队,用 ele
保存出队元素的值,然后返回 1 表示出队成功;如果是空队列,则返回 0 表示出队失败。
实现步骤:
front
和队尾指针 rear
都指向 NULL
。因为只有一个元素出队后,队列就会变成空队列。front
指向下一个结点。实现代码如下:
/**
* 将元素出队
* @param queue 链队列
* @param ele 用来保存出队元素
* @return 如果链队列为空则不能出队则返回 0 表示出队失败;否则返回 1 表示出队成功
*/
int deQueue(LinkedQueue **queue, int *ele) {
// 0.参数校验,如果队空则不能出队
if ((*queue)->front == NULL || (*queue)->rear == NULL) {
return 0;
}
// 1.将队头结点出队
// 1.1 变量,记录链队列的队头结点
QNode *frontNode = (*queue)->front;
// 1.2 用 ele 保存队头结点的数据域,即要出队的结点值
*ele = frontNode->data;
// 1.3 删除队头结点并修改队头指针
// 1.3.1 如果队列中只有一个结点时的出队操作需要特殊处理,因为需要将队头指针和队尾指针都指向 NULL
if ((*queue)->front == (*queue)->rear) {
(*queue)->front = NULL;
(*queue)->rear = NULL;
}
// 1.3.2 当队列不止一个结点时,将队头指针指向原队头结点的后继结点
else {
(*queue)->front = frontNode->next;
}
// 1.4 释放原队头结点
free(frontNode);
// 1.5 返回 1 表示出队成功
return 1;
}
size
统计链队中的结点个数。
实现步骤:
声明局部变量 len
,用于记录链队的结点个数。
从队头结点(即队头指针所指向的结点)开始扫描整个单链表,每个结点出现一次那么变量 len
便加一。
最后返回统计结果。
实现代码如下:
/**
* 获取链队列中的结点个数
* @param queue 链队列
* @return 结点个数
*/
int size(LinkedQueue *queue) {
// 变量,记录链队列结点个数
int len = 0;
// 变量,结点链队列的队头结点,相当于单链表的开始结点
QNode *node = queue->front;
// 扫描链队列,即遍历单链表,统计结点个数
while (node != NULL) {
len++;
node = node->next;
}
return len;
}
getFront
读取队头元素。如果队列非空,则获取队头结点的元素值,用 ele 保存,然后返回 1 表示获取成功。如果队列为空则返回 0 表示获取失败。
实现步骤:
实现代码如下:
/**
* 获取链队列的队头结点数据值
* @param queue 链队列
* @param ele 用来保存队头结点数据值
* @return 如果链队列为空则返回 0 表示获取失败,否则返回 1 表示获取成功
*/
int getFront(LinkedQueue *queue, int *ele) {
// 0.参数校验,如果链队列为空,则表示不能获取队头元素
if (queue->front == NULL || queue->rear == NULL) {
return 0;
}
// 1.用 ele 保存队头结点的数据值,即队头指针所指向的结点
*ele = queue->front->data;
return 1;
}
getRear
读取队尾元素。如果队列非空,则获取队尾结点的元素值,用 ele 保存,然后返回 1 表示获取成功。如果队列为空则返回 0 表示获取失败。
实现步骤:
实现代码如下:
/**
* 获取链队列的队尾结点数据值
* @param queue 链队列
* @param ele 用来保存队尾结点数据值
* @return 如果链队列为空则返回 0 表示获取失败,否则返回 1 表示获取成功
*/
int getRear(LinkedQueue *queue, int *ele) {
// 0.参数校验,如果链队列为空,则表示不能获取队尾元素
if (queue->front == NULL || queue->rear == NULL) {
return 0;
}
// 1.用 ele 保存队尾结点的数据值,即队尾指针所指向的结点
*ele = queue->rear->data;
return 1;
}
print
打印链队中所有元素。
实现步骤:
实现代码如下:
/**
* 打印链队列的所有结点值
* @param queue 链队列
*/
void print(LinkedQueue *queue) {
printf("[");
QNode *frontNode = queue->front;
while (frontNode != NULL) {
printf("%d", frontNode->data);
if (frontNode->next != NULL) {
printf(", ");
}
frontNode = frontNode->next;
}
printf("]\n");
}
clear
清空链队。
实现步骤:
front
和队尾指针 rear
指向 NULL
,表示是空队。实现代码如下:
/**
* 清空链队列
* @param queue 链队列
*/
void clear(LinkedQueue **queue) {
// 变量,记录链队列中的节点,初始为链队列的队头结点
QNode *frontNode = (*queue)->front;
// 扫描链队列所有结点,释放每个结点的空间
while (frontNode != (*queue)->rear) {
// 保存当前节点的后继结点,因为要释放结点,所以需要提前保存
QNode *temp = frontNode->next;
// 释放当前节点
free(frontNode);
// 继续链队列的下一个结点
frontNode = temp;
}
// 最后链队列头结点的队头指针和队尾指针都指向 NULL 表示空队列
(*queue)->front = NULL;
(*queue)->rear = NULL;
}
destroy
销毁链队。
实现步骤:
实现代码如下:
/**
* 销毁链队列
* @param queue 链队列
*/
void destroy(LinkedQueue **queue) {
// 即释放链队列头结点的空间
free(*queue);
}
无。