1.基本概念
队列是最常见的概念,日常生活经常需要排队,仔细观察队列会发现,队列是一种逻辑结构,是一种特殊的线性表。特殊在:
- 只能在固定的两端操作线性表
只要满足上述条件,那么这种特殊的线性表就会呈现一种“先进先出”的逻辑,这种逻辑就被称为队列。
由于约定了只能在线性表固定的两端进行操作,于是给队列这种特殊的线性表的插入删除,起个特殊的名称:
- 队头:可以删除节点的一端
- 队尾:可以插入节点的一端
- 入队:将节点插入到队尾之后,函数名通常为enQueue()
- 出队:将队头节点从队列中剔除,函数名通常为outQueue()
- 取队头:取得队头元素,但不出队,函数名通常为front()
由于这种固定两端操作的简单约定,队列获得了“先进先出”(FIFO)的基本特性,如下图所示:
2.链式队列操作:
链式队列(一个单向链表)的组织形式与链表无异,只不过插入删除被约束在固定的两端。为了便于操作,通常也会创建所谓管理结构体,用来存储队头指针、队尾指针、队列元素个数等信息:
从上图可以看到,链式队列主要控制队头和队尾,由于管理结构体中保存了当前队列元素个数size,因此可以不必设计链表的头节点,初始化空队列时只需要让队头队尾指针同时指向空即可。
2.1 节点设计:
以下是队列链表节点设计和管理结构体设计的示例代码:
// 链队列节点设计
typedef struct node
{
int data;
struct node *next;
} Node_t, *P_Node_t;
// 管理链队列结构体
typedef struct linkQueue
{
int size; // 链队节点个数
struct node *front; // 队头指针
struct node *rear; // 队尾指针
} linkQueue;
2.2链队初始化:
/// @brief 管理链队结构体初始化
/// @return 返回新的链队管理结构体
linkQueue *queueInit()
{
// 管理链队结构体申请堆内存空间
linkQueue *q = (linkQueue *)calloc(1, sizeof(linkQueue));
// 判断链队管理结构体堆内存空间是否申请成功
if (q == NULL)
{
printf("管理链队结构体堆内存空间失败.\n");
return NULL;
}
// 链队队头指针指向NULL
q->front = NULL;
// 链队对尾指针指向NULL
q->rear = NULL;
// 链队节点个数初始化为0
q->size = 0;
// 返回管理链队结构体指针
return q;
}
/// @brief 链队节点初始化
/// @param newData 新数据
/// @return 返回新节点
P_Node_t nodeInit(int newData)
{
// 申请新节点堆内存空间
P_Node_t newNode = (P_Node_t)calloc(1, sizeof(Node_t));
// 判断新节点堆内存空间是否申请成功
if (newNode == NULL)
{
printf("新节点堆内存空间申请失败.\n");
return NULL;
}
// 新节点数据初始化
newNode->data = newData;
// 新节点next指向NULL
newNode->next = NULL;
// 返回新节点
return newNode;
}
2.3入队
/// @brief 新节点入队
/// @param q 链式队列管理结构体指针
/// @param newNode 新节点
/// @return 入队成功返回真,失败返回假
bool enQueue(linkQueue *q, P_Node_t newNode)
{
// 判断队列或者新节点是否为NULL
if (q == NULL || newNode == NULL)
{
printf("管理链队结构体或新节点为空.\n");
return false;
}
// 链式队列入队要考虑到链队是否为空
if (q->size == 0)
{
// 当链队为空时,队头队尾指针都指向新节点
q->front = newNode;
q->rear = newNode;
}
else
{
// 当链队不为空时
// 队尾指针的next指针指向新节点
q->rear->next = newNode;
// 队尾指针指向新节点
q->rear = newNode;
}
// 节点个数+1
q->size++;
// 节点入队返回真
return true;
}
2.4出队
/// @brief 节点出队
/// @param q 管理链队结构体
/// @param outData 出队的节点数据
/// @return 出队成功返回真,失败返回假
bool outQueue(linkQueue *q, int *outData)
{
// 当链队无节点出队时
if (q->size == 0)
{
printf("链队为空.\n");
return false;
}
// tmp指向队头节点,即要出队节点
P_Node_t tmp = q->front;
// 将出队节点数据赋给*outData
*outData = tmp->data;
// 队头指针指向下一个节点
q->front = q->front->next;
// 链队节点个数-1
q->size--;
// 当链队节点为0时
if (q->size == 0)
{
// 链队的队头队尾指针指向NULL
q->front = q->rear = NULL;
}
// 释放tmp出队节点
free(tmp);
// 出队成功返回真
return true;
}
2.5遍历链队
/// @brief 遍历节点
/// @param q 管理链队结构体
void display(linkQueue *q)
{
// q->size时链队为空
if (q->size == 0)
{
printf("链队无数据.\n");
return;
}
printf("当前队列为:\n");
// tmp遍历链队
for (P_Node_t tmp = q->front; tmp != NULL; tmp = tmp->next)
{
printf("%d\n", tmp->data);
}
return;
}
2.6 释放链队
// 释放链队节点堆内存空间
while (q->size != 0)
{
outQueue(q, &outData);
}
// 释放管理链队结构体
free(q);
2.7代码示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
// 链队列节点设计
typedef struct node
{
int data;
struct node *next;
} Node_t, *P_Node_t;
// 管理链队列结构体
typedef struct linkQueue
{
int size; // 链队节点个数
struct node *front; // 队头指针
struct node *rear; // 队尾指针
} linkQueue;
/// @brief 管理链队结构体初始化
/// @return 返回新的链队管理结构体
linkQueue *queueInit()
{
// 管理链队结构体申请堆内存空间
linkQueue *q = (linkQueue *)calloc(1, sizeof(linkQueue));
// 判断链队管理结构体堆内存空间是否申请成功
if (q == NULL)
{
printf("管理链队结构体堆内存空间失败.\n");
return NULL;
}
// 链队队头指针指向NULL
q->front = NULL;
// 链队对尾指针指向NULL
q->rear = NULL;
// 链队节点个数初始化为0
q->size = 0;
// 返回管理链队结构体指针
return q;
}
/// @brief 链队节点初始化
/// @param newData 新数据
/// @return 返回新节点
P_Node_t nodeInit(int newData)
{
// 申请新节点堆内存空间
P_Node_t newNode = (P_Node_t)calloc(1, sizeof(Node_t));
// 判断新节点堆内存空间是否申请成功
if (newNode == NULL)
{
printf("新节点堆内存空间申请失败.\n");
return NULL;
}
// 新节点数据初始化
newNode->data = newData;
// 新节点next指向NULL
newNode->next = NULL;
// 返回新节点
return newNode;
}
/// @brief 新节点入队
/// @param q 链式队列管理结构体指针
/// @param newNode 新节点
/// @return 入队成功返回真,失败返回假
bool enQueue(linkQueue *q, P_Node_t newNode)
{
// 判断队列或者新节点是否为NULL
if (q == NULL || newNode == NULL)
{
printf("管理链队结构体或新节点为空.\n");
return false;
}
// 链式队列入队要考虑到链队是否为空
if (q->size == 0)
{
// 当链队为空时,队头队尾指针都指向新节点
q->front = newNode;
q->rear = newNode;
}
else
{
// 当链队不为空时
// 队尾指针的next指针指向新节点
q->rear->next = newNode;
// 队尾指针指向新节点
q->rear = newNode;
}
// 节点个数+1
q->size++;
// 节点入队返回真
return true;
}
/// @brief 节点出队
/// @param q 管理链队结构体
/// @param outData 出队的节点数据
/// @return 出队成功返回真,失败返回假
bool outQueue(linkQueue *q, int *outData)
{
// 当链队无节点出队时
if (q->size == 0)
{
printf("链队为空.\n");
return false;
}
// tmp指向队头节点,即要出队节点
P_Node_t tmp = q->front;
// 将出队节点数据赋给*outData
*outData = tmp->data;
// 队头指针指向下一个节点
q->front = q->front->next;
// 链队节点个数-1
q->size--;
// 当链队节点为0时
if (q->size == 0)
{
// 链队的队头队尾指针指向NULL
q->front = q->rear = NULL;
}
// 释放tmp出队节点
free(tmp);
// 出队成功返回真
return true;
}
/// @brief 遍历节点
/// @param q 管理链队结构体
void display(linkQueue *q)
{
// q->size时链队为空
if (q->size == 0)
{
printf("链队无数据.\n");
return;
}
printf("当前队列为:\n");
// tmp遍历链队
for (P_Node_t tmp = q->front; tmp != NULL; tmp = tmp->next)
{
printf("%d\t", tmp->data);
}
return;
}
int main(int argc, char const *argv[])
{
// 初始化管理链队结构体
linkQueue *q = queueInit();
char optMenu;
P_Node_t newNode = NULL;
int data, outData = 0, overFlag = 0;
while (1)
{
printf("输入i入队\t输入o出队\t输入d打印当前队列\tt.退出\n");
scanf("%c", &optMenu);
switch (optMenu)
{
case 'i':
printf("请输入数据入队:\n");
scanf("%d", &data);
newNode = nodeInit(data);
if (enQueue(q, newNode))
{
printf("入队成功.\n");
}
else
{
printf("入队失败.\n");
}
break;
case 'o':
if (outQueue(q, &outData))
{
printf("出队成功,出队的数据为:%d\n", outData);
}
else
{
printf("出队失败.\n");
}
break;
case 'd':
display(q);
printf("\n");
break;
case 't':
overFlag = 1;
break;
default:
printf("输入错误,请重新输入.\n");
break;
}
if (overFlag)
{
break;
}
while (getchar() != '\n')
;
}
// 释放链队节点堆内存空间
while (q->size != 0)
{
outQueue(q, &outData);
}
// 释放管理链队结构体
free(q);
return 0;
}
3.结语
链式队列是一种灵活且高效的数据结构,适用于各种需要动态存储管理和高效数据操作的场景。与数组实现的队列相比,链式队列在尺寸管理和内存利用上具有明显优势,能够动态地扩展或收缩,从而有效应对不确定的数据量。
本文探讨了链式队列的基本结构、操作方法以及在实际应用中的优势。通过对链式队列的掌握,开发者可以更好地处理各种任务调度、资源管理等场景下的数据流动需求