一、队列的基本概念
在计算机科学里,队列是一种极为基础且重要的数据结构,它遵循先进先出(First-In-First-Out,FIFO)的原则。这一原则就如同日常生活中排队等待服务的场景,先到的人会先接受服务,而后到的人则需在队列尾部等待。在计算机系统中,队列被广泛应用于各类场景,比如任务调度、消息传递、缓冲区管理等。
1.1 队列的操作
队列主要有两种基本操作:
- 入队(Enqueue):也被称作插入操作,它的作用是将一个新元素添加到队列的尾部。
- 出队(Dequeue):也叫做删除操作,它会移除队列头部的元素。
1.2 队列的特点
- 顺序性:元素进入队列和离开队列的顺序是固定的,先进入队列的元素会先离开。
- 受限访问:只能在队列的头部进行出队操作,在队列的尾部进行入队操作,不允许在队列中间进行插入或删除操作。
二、链式队列的实现思路
队列可以通过多种方式来实现,其中链式队列是一种常见的实现方式。链式队列借助链表来存储元素,每个节点包含数据和指向下一个节点的指针。这种实现方式的优点在于可以动态地分配内存,不会受到固定大小的限制。
2.1 链式队列的结构
链式队列主要由两部分构成:
- 节点(Node):每个节点包含一个数据元素和一个指向下一个节点的指针。
- 队列(Queue):队列包含两个指针,分别指向队列的头部和尾部。
2.2 链式队列的操作
链式队列的操作和普通队列的操作基本一致,主要包括初始化、入队、出队、获取队列头部和尾部元素、检查队列是否为空、销毁队列以及打印队列等操作。
三、代码实现
3.1 Queue.h
文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
// 链式结构:表示队列
typedef int QDataType;
typedef struct QListNode
{
struct QListNode* _next;
QDataType _data;
}QListNode;
// 队列的结构
typedef struct Queue
{
QListNode* _front;
QListNode* _rear;
//int size; //队列中有效数据个数
}Queue;
// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
// 打印队列
void QueuePrint(Queue* q);
详细解释
QDataType
:把int
重命名为QDataType
,这样做的好处是在后续修改队列存储的数据类型时会更加方便。例如,如果需要存储char
类型的数据,只需要将typedef int QDataType;
改为typedef char QDataType;
即可。QListNode
:代表队列中的一个节点,它包含一个指向下一个节点的指针_next
以及存储的数据_data
。通过这种方式,节点之间可以形成一个链表。Queue
:表示队列,包含指向队列头部的指针_front
和指向队列尾部的指针_rear
。这两个指针可以方便地进行入队和出队操作。- 函数声明:对队列的各项操作函数进行了声明,这些函数涵盖了队列的基本操作,为后续的实现提供了接口。
3.2 Queue.c
文件
QueueInit
函数(初始化队列)
// 初始化队列
void QueueInit(Queue* q) {
assert(q);
q->_front = q->_rear = NULL;
//q->size = 0;
}
- 功能:对队列进行初始化。
- 参数:
q
是指向队列的指针。 - 详细解释:使用
assert
确保q
不为空指针,这是为了避免对空指针进行操作而导致程序崩溃。然后将队列的头部和尾部指针都设为NULL
,这表明队列为空。如果队列中有size
字段,还可以将其初始化为 0。
QueuePush
函数(队尾入队列)
// 队尾入队列
void QueuePush(Queue* q, QDataType data) {
assert(q);
QListNode* newnode = (QListNode*)malloc(sizeof(QListNode));
if (newnode == NULL) {
perror("malloc fail");
exit(1);
}
newnode->_data = data;
newnode->_next = NULL;
if (q->_front == NULL) {
q->_front = q->_rear = newnode;
}
else {
q->_rear->_next = newnode;
q->_rear = newnode;
}
//q->size++;
}
以下是if (q->_front == NULL) 和else执行的二个方法,也就是为空和不为空的插入(主要判断队列中也没有结点)
- 功能:在队列尾部添加一个新元素。
- 参数:
q
是指向队列的指针,data
是要添加的元素。 - 详细解释:
- 利用
assert
确保q
不为空指针。 - 运用
malloc
为新节点分配内存,若分配失败则输出错误信息并终止程序。这是因为内存分配失败可能会导致后续操作无法正常进行。 - 对新节点的数据和
_next
指针进行初始化。 - 若队列为空,就把队列的头部和尾部指针都指向新节点;反之,将新节点添加到队列尾部并更新尾部指针。如果队列中有
size
字段,还需要将其加 1。
- 利用
QueueEmpty
函数(检测队列是否为空)
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q) {
assert(q);
return q->_front == NULL;
}
- 功能:检查队列是否为空。
- 参数:
q
是指向队列的指针。 - 详细解释:使用
assert
确保q
不为空指针,若队列头部指针为NULL
则表明队列为空,返回true
;反之返回false
。
QueuePop
函数(队头出队列)
// 队头出队列
void QueuePop(Queue* q) {
assert(!QueueEmpty(q));
if (q->_front == q->_rear)
{
free(q->_front);
q->_front = q->_rear = NULL;
}
else {
QListNode* next = q->_front->_next;
free(q->_front);
q->_front = next;
}
//q->size--;
}
以下是if (q->_front == q->_rear) 和else执行的二个方法,也就是检查是否为一个结点,如果是就把队头置为空最后队头和队尾置为NULL,如果不是就把队头的下一个指的指针保存到新的临时结点中,最后重新赋给队头。
- 功能:移除队列头部的元素。
- 参数:
q
是指向队列的指针。 - 详细解释:
- 利用
assert
确保队列不为空。 - 若队列中只有一个元素,释放该元素并将队列的头部和尾部指针都设为
NULL
。 - 若队列中有多个元素,释放头部元素并更新头部指针。如果队列中有
size
字段,还需要将其减 1。
- 利用
QueueFront
函数(获取队列头部元素)
// 获取队列头部元素
QDataType QueueFront(Queue* q) {
assert(!QueueEmpty(q));
return q->_front->_data;
}
- 功能:获取队列头部的元素。
- 参数:
q
是指向队列的指针。 - 详细解释:使用
assert
确保队列不为空,然后返回队列头部元素的数据。
QueueBack
函数(获取队列队尾元素)
// 获取队列队尾元素
QDataType QueueBack(Queue* q) {
assert(!QueueEmpty(q));
return q->_rear->_data;
}
- 功能:获取队列尾部的元素。
- 参数:
q
是指向队列的指针。 - 详细解释:使用
assert
确保队列不为空,然后返回队列尾部元素的数据。
QueueSize
函数(获取队列中有效元素个数)
// 获取队列中有效元素个数
int QueueSize(Queue* q) {
assert(q);
QListNode* pcur = q->_front;
int size = 0;
while (pcur) {
size++;
pcur = pcur->_next;
}
return size;
//return q->size;
}
- 功能:获取队列中有效元素的个数。
- 参数:
q
是指向队列的指针。 - 详细解释:
- 运用
assert
确保q
不为空指针。 - 通过遍历队列的方式统计元素个数。如果队列中有
size
字段,也可以直接返回该字段的值。
- 运用
QueueDestroy
函数(销毁队列)
// 销毁队列
void QueueDestroy(Queue* q) {
assert(q);
QListNode* pcur = q->_front;
while (pcur)
{
QListNode* next = pcur->_next;
free(pcur);
pcur = next;
}
q->_front = q->_rear = NULL;
//pq->size = 0;
}
- 功能:销毁队列,释放队列中所有节点的内存。
- 参数:
q
是指向队列的指针。 - 详细解释:
- 利用
assert
确保q
不为空指针。 - 遍历队列,释放每个节点的内存。
- 最后把队列的头部和尾部指针都设为
NULL
。如果队列中有size
字段,还需要将其设为 0。
- 利用
QueuePrint
函数(打印队列)
//打印队列
void QueuePrint(Queue * q) {
assert(q);
QListNode* pcur = q->_front;
while (pcur) {
printf("%d ", pcur->_data);
pcur = pcur->_next;
}
printf("\n");
}
- 功能:打印队列中的所有元素。
- 参数:
q
是指向队列的指针。 - 详细解释:
- 运用
assert
确保q
不为空指针。 - 遍历队列,打印每个节点的数据。
- 运用
3.3 test.c
文件
#include"Queue.h"
void test01() {
Queue plist;
QueueInit(&plist);
QueuePush(&plist, 1);
QueuePush(&plist, 2);
QueuePush(&plist, 3);
// 调用打印函数
QueuePrint(&plist);
//QueuePop(&q);
int front = QueueFront(&plist);
int rear = QueueBack(&plist);
printf("front:%d\n", front);
printf("rear:%d\n", rear);
printf("size:%d\n", QueueSize(&plist));
QueueDestroy(&plist);
}
int main() {
test01();
return 0;
}
详细解释
test01
函数:- 对队列进行初始化。
- 向队列中添加三个元素。
- 打印队列中的元素。
- 获取队列的头部和尾部元素并打印。
- 获取队列的元素个数并打印。
- 销毁队列。
main
函数:调用test01
函数进行测试。
四、应用场景
-
括号匹配:遍历字符串,左括号入栈,右括号与栈顶匹配。
-
函数调用栈:系统栈用于保存函数调用现场。
-
逆波兰表达式:用栈实现表达式求值。
五、总结
通过上述代码,我们实现了一个基于链式结构的队列,并且对队列的各项操作函数进行了详细的实现和测试。在实际应用中,队列可以用来解决很多问题,例如任务调度、消息处理等。掌握队列的基本操作和实现原理,对于学习数据结构和算法是非常有帮助的。
希望本文能帮助你更好地理解链式队列的实现和应用。如果你有任何疑问或者建议,欢迎在评论区留言。
附上代码Queue.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
// 链式结构:表示队列
typedef int QDataType;
typedef struct QListNode
{
struct QListNode* _next;
QDataType _data;
}QListNode;
// 队列的结构
typedef struct Queue
{
QListNode* _front;
QListNode* _rear;
//int size; //队列中有效数据个数
}Queue;
// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
// 打印队列
void QueuePrint(Queue* q);
代码:Queue.c
#include"Queue.h"
// 初始化队列
void QueueInit(Queue* q) {
assert(q);
q->_front = q->_rear = NULL;
//q->size = 0;
}
// 队尾入队列
void QueuePush(Queue* q, QDataType data) {
assert(q);
QListNode* newnode = (QListNode*)malloc(sizeof(QListNode));
if (newnode == NULL) {
perror("malloc fail");
exit(1);
}
newnode->_data = data;
newnode->_next = NULL;
if (q->_front == NULL) {
q->_front = q->_rear = newnode;
}
else {
q->_rear->_next = newnode;
q->_rear = newnode;
}
//q->size++;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q) {
assert(q);
return q->_front == NULL;
}
// 队头出队列
void QueuePop(Queue* q) {
assert(!QueueEmpty(q));
if (q->_front == q->_rear)
{
free(q->_front);
q->_front = q->_rear = NULL;
}
else {
QListNode* next = q->_front->_next;
free(q->_front);
q->_front = next;
}
//q->size--;
}
// 获取队列头部元素
QDataType QueueFront(Queue* q) {
assert(!QueueEmpty(q));
return q->_front->_data;
}
// 获取队列队尾元素
QDataType QueueBack(Queue* q) {
assert(!QueueEmpty(q));
return q->_rear->_data;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q) {
assert(q);
QListNode* pcur = q->_front;
int size = 0;
while (pcur) {
size++;
pcur = pcur->_next;
}
return size;
//return q->size;
}
// 销毁队列
void QueueDestroy(Queue* q) {
assert(q);
QListNode* pcur = q->_front;
while (pcur)
{
QListNode* next = pcur->_next;
free(pcur);
pcur = next;
}
q->_front = q->_rear = NULL;
//pq->size = 0;
}
//打印队列
void QueuePrint(Queue * q) {
assert(q);
QListNode* pcur = q->_front;
while (pcur) {
printf("%d ", pcur->_data);
pcur = pcur->_next;
}
printf("\n");
}
代码:test.c
#include"Queue.h"
void test01() {
Queue plist;
QueueInit(&plist);
QueuePush(&plist, 1);
QueuePush(&plist, 2);
QueuePush(&plist, 3);
// 调用打印函数
QueuePrint(&plist);
//QueuePop(&q);
int front = QueueFront(&plist);
int rear = QueueBack(&plist);
printf("front:%d\n", front);
printf("rear:%d\n", rear);
printf("size:%d\n", QueueSize(&plist));
QueueDestroy(&plist);
}
int main() {
test01();
return 0;
}