文章目录
队列的链式实现
链队列是队列的链式存储结构,本质上是操作受限 的单链表 。其解决了顺序队列(循环队列)"容量固定、可能溢出"的缺陷。
链队列需要两个指针:
- front (队头指针):指向头结点(注意:不是第一个数据结点)。头结点的
data闲置,next指向真正的首元结点。 - rear(队尾指针):指向最后一个数据结点(尾结点)。
带头结点后 ,空队时 front == rear(均指向头结点),所有操作逻辑完全统一。
图示:
text
空队: front(rear) -> [头结点 | next=NULL]
入队10: front -> [头结点 | next] -> [10 | NULL]
rear-^
入队20: front -> [头结点 | next] -> [10 | next] -> [20 | NULL]
rear -^
代码实现
结构定义与初始化
与顺序队列不同,链队列通常将front和rear封装在一个结构体中,便于函数传参。
带头结点

cpp
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// ① 结点定义(与单链表完全一样)
typedef struct LNode {
int data;
struct LNode *next;
} LNode;
// ② 链队列定义(封装了队头和队尾指针)
typedef struct {
LNode *front; // 队头指针(指向头结点)
LNode *rear; // 队尾指针(指向最后一个结点)
} LinkQueue;
// 带头结点
// 初始化 :创建空队列(创建头结点)
bool InitQueue(LinkQueue &Q) {
// 1. 申请头结点空间 队尾指针也指向头结点(表示空队)
Q.front = Q.rear = (LNode *)malloc(sizeof(LNode));
if (Q.front == NULL)
return false; // 内存分配失败
// 2. 头结点的 next 置空
Q.front->next = NULL;
return true;
}
// 判空
bool QueueEmpty(LinkQueue Q) {
return Q.front == Q.rear; // 或者 Q.front->next == NULL 若 front = rear 则为空
}
// 判满 链队列不存在 "满 "的概念!
// 唯一的限制是堆内存耗尽。通常不写判满函数,而是在入队时检测 malloc 返回值。
bool QueueFull(LinkQueue Q) {
return false; // 理论永远不满(只要内存够)
}
不带头结点

cpp
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct LNode {
int data;
struct LNode *next;
} LNode;
typedef struct {
LNode *front; // 队头指针:指向第一个数据结点(不是头结点!!!!!)
LNode *rear; // 队尾指针:指向最后一个数据结点
} LinkQueue;
// 不带头结点
// 初始化 创建空队列(不分配头结点)
void InitQueue(LinkQueue &Q) {
Q.front = NULL; // 直接置空
Q.rear = NULL; // 直接置空
}
// 判空
bool QueueEmpty(LinkQueue Q) {
return Q.front == NULL; // 只要队头为空,队列就为空
// 也可以这样判断:Q.rear == NULL;
}
入队(尾插法)
带头结点
步骤:新建结点 → 接到 rear 后面 → 更新rear为新结点

cpp
// 带头结点
// 入队 在队尾(rear 后面)插入元素 e
bool EnQueue(LinkQueue &Q, int e) {
// 1. 申请新结点空间
LNode *s = (LNode *)malloc(sizeof(LNode));
if (s == NULL)
return false; // 内存耗尽,相当于 "队满 "
// 2. 初始化新结点
s->data = e;
s->next = NULL;
// 3. 将新结点挂到当前尾结点后面
Q.rear->next = s;
// 4. 尾指针后移(指向新的尾结点)
Q.rear = s;
return true;
}
不带头结点

cpp
// 不带头结点
// 入队 在队尾插入元素 e
bool EnQueue(LinkQueue &Q, int e) {
LNode *s = (LNode *)malloc(sizeof(LNode));
if (s == NULL)
return false;
s->data = e;
s->next = NULL;
// 必须特别判断是不是第一个结点
// 不带头结点的队列,第一个元素入队时需要特别处理
if (Q.front == NULL) {
// 情况1:空队,front 和 rear 都指向新结点
Q.front = s; // 修改队头队尾指针
Q.rear = s;
} else {
// 情况2:非空队,挂在尾结点后面
Q.rear->next = s; // 新结点插入到 rear 结点之后
Q.rear = s; // 修改 rear 指针
}
return true;
}
出队
删除队头第一个有效结点
特殊边界:如果删除后队列为空,需要将rear重置回头结点,防止 rear 悬空。
带头结点
删除头结点的后继。


cpp
// 带头结点
// 出队 删除队头元素,并通过 e 带回其值
bool DeQueue(LinkQueue &Q, int &e) {
// 1. 判空
if (Q.front == Q.rear)
return false;
// 2. p 指向首元结点(第一个数据结点)
LNode *p = Q.front->next;
e = p->data; // 保存数据
// 3. 头结点跨过 p,指向 p 的下一个结点
Q.front->next = p->next;
// 注意:如果 p 恰好是最后一个结点(即出队后队列变空)
// 必须把 rear 重新指向头结点,否则 rear 将指向已释放的内存!
if (p == Q.rear) {
Q.rear = Q.front;
}
// 4. 释放 p 结点内存
free(p);
return true;
}
不带头结点

cpp
// 不带头结点
// 出队 删除队头元素,并通过 e 带回其值
bool DeQueue(LinkQueue &Q, int &e) {
// 1. 判空
if (Q.front == NULL)
return false;
// 2. p 指向队头结点(直接就是 front)
LNode *p = Q.front;
e = p->data;
// 3. 队头指针后移(指向下一个数据结点)
Q.front = p->next;
// 如果删除的是唯一结点(出队后队列变空)
if (Q.front == NULL) {
// 必须把 rear 也置为 NULL,否则 rear 指向已释放的内存!
Q.rear = NULL;
}
// 或这样写
// if(Q.rear == p ){
// Q.rear = NULL;
// Q.front = NULL;
// }
free(p);
return true;
}
查队头
cpp
// 带头结点
// 查队头 只读操作,不删除元素
bool GetHead(LinkQueue Q, int &e) {
if (Q.front == Q.rear) // 空队
return false;
e = Q.front->next->data; // 头结点的后继才是第一个数据
return true;
}
// 不带头结点
// 查队头 只读操作
bool GetHead(LinkQueue Q, int &e) {
if (Q.front == NULL)
return false;
e = Q.front->data; // front 直接指向数据结点,无需绕 ->next
return true;
}
销毁
cpp
// 带头结点
// 销毁 释放所有结点(包括头结点)
void DestroyQueue(LinkQueue &Q) {
LNode *p = Q.front; // p 从头结点开始
while (p != NULL) {
LNode *q = p;
p = p->next;
free(q);
}
// 防止野指针(虽然 Q 是引用,但置空是好习惯)
Q.front = NULL;
Q.rear = NULL;
}
// 不带头结点
// 销毁 释放所有数据结点
void DestroyQueue(LinkQueue &Q) {
LNode *p = Q.front;
while (p != NULL) {
LNode *q = p;
p = p->next;
free(q);
}
Q.front = NULL;
Q.rear = NULL;
}
链队列是采用链式存储结构的队列,通常使用带头结点的单链表实现,并附设 front 和 rear 两个指针分别指向头结点和尾结点。入队操作对应单链表的尾插法(rear->next = s; rear = s;),出队操作对应删除头结点的后继结点。在出队时,若被删结点是尾结点,必须将 rear 重置为 front。链队列克服了顺序队列容量固定的缺陷,入队、出队、判空操作的时间复杂度均为 O(1)。