上一节:【数据结构与算法从0到1 - C实现】四、栈的解析与实现
下一节:待续……
目录
1 队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)的特性;
入队列:进行插入操作的一端称为队尾;
出队列:进行删除操作的一端称为队头;
数据结构中的队列,很容易会让我们想到生活中的队列,比如饭堂排队打饭的场景。新来的打饭的人相当于入队列的数据,位于队尾;刚打完饭要离开的人相当于出队列的数据,位于队头。
2 队列的C实现
队列的实现通过数组(顺序表)和链表都可以实现,但使用链表的结构实现更好一些。
因为对于数组来说,不论是前端作为队头,还是后端作为队头,队头和队尾必定会有一个需要让整个数组移动的情况,这样就会造成比较大的消耗。
而链表就没有这样的烦恼,可以直接在尾端(或头端)删掉(或增加)数据,非常方便。但对于一般单链表还有个缺陷,在每次尾端增加数据(入队列)时,都要从头进行遍历链表,以获得链表末端地址。基于这个原因,我们可以对单链表进行改造,增加一个尾节点,专门定位链表尾部地址。那么我们在构建队列的时候,可以使用一个结构体,该结构体中包含链表的头结点和尾节点,头结点主要负责数据出队列,尾节点主要负责数据入队列。
创建头文件Queue.h,完成链表式队列的节点的定义,队列结构体的定义,以及各个队列接口函数的声明;这些接口函数有:队列的初始化、销毁、入队列、出队列、返回头部数据、返回尾部数据、返回队列的大小、判断队列是否为空。
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDataType;
// 链表式队列结构的节点
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
// 队列结构体(包含了头和尾)
typedef struct Queue
{
QNode* head;
QNode* tail;
}Queue;
// 初始化队列
void QueueInit(Queue* pq);
// 销毁队列
void QueueDestroy(Queue* pq);
// 入队列
void QueuePush(Queue* pq, QDataType x);
// 出队列
void QueuePop(Queue* pq);
// 取队头数据
QDataType QueueFront(Queue* pq);
// 取队尾数据
QDataType QueueBack(Queue* pq);
// 计算队列中有多少个数据
int QueueSize(Queue* pq);
// 判断队列是否为空
bool QueueEmpty(Queue* pq);
创建Queue.c文件,完成各个接口函数的实现:
#define _CRT_SECURE_NO_WARNINGS
#include "Queue.h"
// 初始化队列
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
// 销毁队列
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur != NULL)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
// 入队列
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newQNode = (QNode*)malloc(sizeof(QNode));
if (newQNode == NULL)
{
perror("malloc fail");
exit(-1);
}
newQNode->data = x;
newQNode->next = NULL;
if (pq->head == NULL)
{
pq->head = pq->tail = newQNode;
}
else
{
pq->tail->next = newQNode;
pq->tail = newQNode;
}
}
// 出队列
void QueuePop(Queue* pq)
{
assert(pq && !QueueEmpty(pq));
QNode* next = pq->head->next;
free(pq->head);
if (next == NULL)
{
pq->tail = NULL;
}
pq->head = next;
}
// 取队头数据
QDataType QueueFront(Queue* pq)
{
assert(pq && !QueueEmpty(pq));
return pq->head->data;
}
// 取队尾数据
QDataType QueueBack(Queue* pq)
{
assert(pq && !QueueEmpty(pq));
return pq->tail->data;
}
// 计算队列中有多少个数据
int QueueSize(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
int size = 0;
while (cur)
{
size++;
cur = cur->next;
}
return size;
}
// 判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
在test.c中测试一下设计的队列结构和功能
#define _CRT_SECURE_NO_WARNINGS
#include "Queue.h"
void Test1()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePush(&q, 5);
while (!QueueEmpty(&q))
{
printf("当前队列的大小为:%d\n", QueueSize(&q));
printf("当前队头为:%d,队尾为:%d\n", QueueFront(&q), QueueBack(&q));
printf("出队列……\n");
QueuePop(&q);
}
}
int main()
{
Test1();
return 0;
}
运行结果如下:
3 练习题
4 结语
队列的实现并非本篇提供的这一种方法,而是其中一种相对简单明了的方法,是结合了链表尾部只能知道其后的地址的特性,便于追加数据(入队列),而其他方法(如使用数组)多少都会更加麻烦一点。
所属分类专栏:数据结构与算法
上一节:【数据结构与算法从0到1 - C实现】四、栈的解析与实现
下一节:待续……