3.2队列
队列是一种重要的线性数据结构,遵循先进先出(FIFO)原则,广泛应用于计算机科学中,如任务调度和缓冲区管理。本文基于王道考研资料,系统介绍队列的基本概念、顺序实现、链式实现、双端队列及其考点,结构丰富,涵盖理论到实践。
一、队列的基本概念
队列(Queue)是只允许在一端进行插入(称为入队),在另一端删除(称为出队)的线性表。重要术语包括队头(front,删除端)、队尾(rear,插入端)和空队列(当队列无元素时)。队列的特点是先进先出(First In First Out, FIFO),即先入队的元素先出队。
基本操作包括:
-
InitQueue(&Q)
:初始化队列,构造空队列Q。 -
DestroyQueue(&Q)
:销毁队列,释放内存。 -
EnQueue(&Q, x)
:入队,若队列未满,将x插入队尾。 -
DeQueue(&Q, &x)
:出队,若队列非空,删除队头元素并用x返回。 -
GetHead(Q, &x)
:读队头元素,若队列非空,将队头元素赋值给x。 -
QueueEmpty(Q)
:判空,若队列空返回true。
队列的使用场景多集中于访问队头元素,如广度优先搜索(BFS)。
二、队列的顺序实现
顺序存储使用数组实现队列,需解决“假溢出”问题,常采用循环队列结构。
初始化操作
初始化时,设置队头指针front
和队尾指针rear
均为0,表示空队列。定义如下:
#define MaxSize 10
typedef struct {ElemType data[MaxSize];int front, rear;
} SqQueue;
入队与出队操作
-
入队:元素插入队尾,
rear
指针后移。在循环队列中,使用模运算实现环形结构:bool EnQueue(SqQueue &Q, ElemType x) {if ((Q.rear + 1) % MaxSize == Q.front) return false; // 队满判断Q.data[Q.rear] = x;Q.rear = (Q.rear + 1) % MaxSize;return true; }
-
出队:删除队头元素,
front
指针后移:bool DeQueue(SqQueue &Q, ElemType &x) {if (Q.rear == Q.front) return false; // 队空判断x = Q.data[Q.front];Q.front = (Q.front + 1) % MaxSize;return true; }
循环队列的判空与判满
循环队列通过牺牲一个存储单元解决队满判断问题:
-
队空条件:
Q.rear == Q.front
-
队满条件:
(Q.rear + 1) % MaxSize == Q.front
队列元素个数计算公式:(rear + MaxSize - front) % MaxSize
。
其他判满方案
-
方案二:增加
size
变量记录当前长度,队满条件为size == MaxSize
。 -
方案三:使用
tag
标记最近操作(0为删除,1为插入),队空时tag == 0
且front == rear
,队满时tag == 1
且front == rear
。
三、队列的链式实现
链式存储使用结点动态分配内存,避免固定大小限制,分为带头结点和不带头结点两种形式。
链式队列结构
typedef struct LinkNode {ElemType data;struct LinkNode *next;
} LinkNode;typedef struct {LinkNode *front, *rear;
} LinkQueue;
初始化操作
-
带头结点:初始化时
front
和rear
均指向头结点,头结点的next
为NULL。void InitQueue(LinkQueue &Q) {Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));Q.front->next = NULL; }
-
不带头结点:初始化时
front
和rear
均为NULL。void InitQueue(LinkQueue &Q) {Q.front = Q.rear = NULL; }
入队操作
-
带头结点:新结点插入
rear
之后,更新rear
指针。void EnQueue(LinkQueue &Q, ElemType x) {LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));s->data = x;s->next = NULL;Q.rear->next = s;Q.rear = s; }
-
不带头结点:需特殊处理空队列情况。若队列空,
front
和rear
均指向新结点;否则插入rear
之后。void EnQueue(LinkQueue &Q, ElemType x) {LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));s->data = x;s->next = NULL;if (Q.front == NULL) {Q.front = Q.rear = s;} else {Q.rear->next = s;Q.rear = s;} }
出队操作
-
带头结点:删除
front->next
结点,若为最后一个结点,需重置rear
为front
。bool DeQueue(LinkQueue &Q, ElemType &x) {if (Q.front == Q.rear) return false;LinkNode *p = Q.front->next;x = p->data;Q.front->next = p->next;if (Q.rear == p) Q.rear = Q.front;free(p);return true; }
-
不带头结点:直接删除
front
结点,更新front
指针。bool DeQueue(LinkQueue &Q, ElemType &x) {if (Q.front == NULL) return false;LinkNode *p = Q.front;x = p->data;Q.front = p->next;if (Q.rear == p) Q.rear = NULL;free(p);return true; }
队列满的条件
链式队列在内存耗尽时队满,而顺序队列在预分配空间用尽时队满。
四、双端队列
双端队列(Deque)允许从两端插入和删除,是队列的变种,可分为输入受限和输出受限类型。
双端队列类型
-
标准双端队列:两端均可插入和删除。
-
输入受限双端队列:只允许一端插入、两端删除。
-
输出受限双端队列:只允许两端插入、一端删除。
双端队列若只使用一端操作,效果等同于栈;若限制为队列模式,则等同于普通队列。
输出序列合法性考点
对于输入序列1,2,3,4,双端队列的输出序列合法性判断是常见考点。栈中合法的序列在双端队列中一定合法,但双端队列可能允许更多序列。例如,输入受限双端队列可输出序列如1,2,3,4或2,1,3,4等,需具体分析操作顺序。
五、知识回顾与重要考点
-
队列核心:FIFO原则,重点掌握顺序和链式实现的判空、判满方法。
-
循环队列:模运算实现,注意判满方案的取舍(如牺牲单元、size变量或tag标记)。
-
链式队列:灵活内存管理,区分带头结点与不带头结点的操作差异。
-
双端队列:理解变种类型的应用场景和序列合法性判断。
-
考题趋势:多结合具体代码或序列分析,强调实践能力。
队列是数据结构的基础,掌握其实现细节和变种有助于应对考研或面试中的综合题型。通过本文的结构化解析,读者可系统复习队列知识,提升解题效率。