1队列 Queue ADT
21. 前提条件
3
4队列是一种线性表。
5它的特点是:先进先出 FIFO
6
7也就是:First In First Out
8
9先进入队列的元素,先出队。
10
11例如:
12
13入队顺序:1 2 3
14
15出队顺序:1 2 3
16
17队列通常支持 EnQueue 入队、DeQueue 出队、GetFront 取队头、IsEmpty 判空等操作;队列一般可以用数组或链表实现。
18
192. 核心概念
20
21队列只允许:
22
23队尾 rear 插入
24队头 front 删除
25
26图示:
27
28front rear
29 ↓ ↓
30 1 -> 2 -> 3 -> 4
31
32操作规律:
33
34入队:从 rear 进去
35出队:从 front 出来
36
373. ADT 操作定义
38
39队列常见 ADT 操作:
40
41InitQueue 初始化队列
42IsEmpty 判断队列是否为空
43EnQueue 入队
44DeQueue 出队
45GetFront 获取队头元素
46DestroyQueue 销毁队列
47
48一、顺序队列
491. 前提条件
50顺序队列就是:用数组实现队列
51
52需要两个下标:
53
54front:队头位置
55rear:队尾后一个位置
562. 存储结构
57
58
59#define MAXSIZE 100
60
61struct Queue {
62 int data[MAXSIZE];
63 int front;
64 int rear;
65};
66
67这里:
68
69data:存放元素
70front:队头下标
71rear:队尾后一个位置
723. 初始化
73void InitQueue(struct Queue* q) {
74 q->front = 0;
75 q->rear = 0;
76}
77
78此时:
79
80front == rear
81
82表示队列为空。
83
844. 判空
85int IsEmpty(struct Queue* q) {
86 return q->front == q->rear;
87}
885. 入队
89int EnQueue(struct Queue* q, int x) {
90 if (q->rear >= MAXSIZE) {
91 return 0;
92 }
93
94 q->data[q->rear] = x;
95 q->rear++;
96
97 return 1;
98}
99
100含义:
101先把元素放到 rear 位置;
102再让 rear 后移。
103
1046. 出队
105int DeQueue(struct Queue* q, int* x) {
106 if (IsEmpty(q)) {
107 return 0;
108 }
109
110 *x = q->data[q->front];
111 q->front++;
112
113 return 1;
114}
115
116含义:
117
118先取 front 位置元素;
119再让 front 后移。
120
1217. 取队头
122int GetFront(struct Queue* q, int* x) {
123 if (IsEmpty(q)) {
124 return 0;
125 }
126
127 *x = q->data[q->front];
128 return 1;
129}
130
131注意:
132
133GetFront 只读取,不删除。
134DeQueue 读取并删除。
135
1368. 顺序队列的问题
137
138普通数组队列会有一个问题:假溢出
139
140例如数组长度是 5:
141
142下标:0 1 2 3 4
143元素:1 2 3 4 5
144
145出队两个:
146
147下标:0 1 2 3 4
148元素:_ _ 3 4 5
149front = 2
150rear = 5
151
152虽然前面有空位,但:
153
154rear == MAXSIZE
155
156不能继续入队。
157
158这就是顺序队列浪费空间的问题。
159
160所以更常用的是:循环队列
161
162二、循环队列
1631. 前提条件:循环队列用数组实现,但把数组看成一个环。
164
165当 rear 到达数组末尾后,可以回到下标 0。
166
167循环队列可以避免普通顺序队列前面出队后留下的空位不能继续使用的问题;数组循环队列通常可以让入队和出队都保持 O(1)。
168
1692. 存储结构
170#define MAXSIZE 100
171
172struct Queue {
173 int data[MAXSIZE];
174 int front;
175 int rear;
176};
177
178仍然是:
179
180front:队头
181rear:队尾后一个位置
1823. 循环移动
183
184普通后移:
185
186q->rear++;
187
188循环后移:
189
190q->rear = (q->rear + 1) % MAXSIZE;
191
192同理:
193
194q->front = (q->front + 1) % MAXSIZE;
1954. 初始化
196void InitQueue(struct Queue* q) {
197 q->front = 0;
198 q->rear = 0;
199}
2005. 判空
201int IsEmpty(struct Queue* q) {
202 return q->front == q->rear;
203}
2046. 判满
205
206循环队列通常故意空出一个位置。
207
208队满条件:
209
210int IsFull(struct Queue* q) {
211 return (q->rear + 1) % MAXSIZE == q->front;
212}
213
214为什么要空一个位置?
215
216因为如果不空位置:
217
218front == rear
219
220既可能表示空,也可能表示满。
221
222所以用:
223
224空一个位置
225
226来区分空和满。
227
2287. 入队
229int EnQueue(struct Queue* q, int x) {
230 if (IsFull(q)) {
231 return 0;
232 }
233
234 q->data[q->rear] = x;
235 q->rear = (q->rear + 1) % MAXSIZE;
236//这里把rear++改成了循环到下一位
237 return 1;
238}
239
2408. 出队
241int DeQueue(struct Queue* q, int* x) {
242 if (IsEmpty(q)) {
243 return 0;
244 }
245
246 *x = q->data[q->front];
247 q->front = (q->front + 1) % MAXSIZE;
248
249 return 1;
250}
251
2529. 取队头
253int GetFront(struct Queue* q, int* x) {
254 if (IsEmpty(q)) {
255 return 0;
256 }
257
258 *x = q->data[q->front];
259 return 1;
260}
261
26210. 循环队列核心
263队空:
264front == rear
265
266队满:
267(rear + 1) % MAXSIZE == front
268
269入队:
270data[rear] = x
271rear = (rear + 1) % MAXSIZE
272
273出队:
274x = data[front]
275front = (front + 1) % MAXSIZE
276三、链式队列
2771. 前提条件
278
279链式队列就是:用链表实现队列
280
281需要两个指针:
282
283front:指向队头结点
284rear:指向队尾结点
285
286队列可以用链表实现,链式队列通常更适合元素数量不固定的情况,因为它可以动态申请节点。
287
2882. 存储结构
289
290struct Node {
291 int data;
292 struct Node* next;
293};
294
295struct LinkQueue {
296 struct Node* front;
297 struct Node* rear;
298};
299
3003. 初始化
301void InitQueue(struct LinkQueue* q) {
302 q->front = NULL;
303 q->rear = NULL;
304}
305
306空队列:
307
308front = NULL
309rear = NULL
310
3114. 判空
312int IsEmpty(struct LinkQueue* q) {
313 return q->front == NULL;
314}
315
3165. 入队
317
318链式队列入队就是:尾插法
319
320代码:
321
322#include <stdlib.h>
323
324int EnQueue(struct LinkQueue* q, int x) {
325 struct Node* node = malloc(sizeof(struct Node));
326
327 if (node == NULL) {
328 return 0;
329 }
330
331 node->data = x;
332 node->next = NULL;
333//这里就是尾插法正常链表
334//下面就是判断先让两者是否正常初始化,之后都指向node
335 if (q->front == NULL) {
336 q->front = node;
337 q->rear = node;
338 } else {
339 q->rear->next = node;
340 q->rear = node;
341 }
342//这里是让rear指向的node的next连到新的node上去,然后移动新的node给rear
343 return 1;
344}
3456. 出队
346
347链式队列出队就是:删除头结点
348
349代码:
350
351int DeQueue(struct LinkQueue* q, int* x) {
352 if (IsEmpty(q)) {
353 return 0;
354 }
355
356 struct Node* p = q->front;
357
358 *x = p->data;
359
360 q->front = p->next;
361//就是把q的front位置data的值赋给x之后front再移动到下一个位置
362 if (q->front == NULL) {
363 q->rear = NULL;
364 }
365
366 free(p);
367
368 return 1;
369}
370
371注意:如果删完之后队列为空,
372rear 也要置 NULL。
373
3747. 取队头
375int GetFront(struct LinkQueue* q, int* x) {
376 if (IsEmpty(q)) {
377 return 0;
378 }
379
380 *x = q->front->data;
381 return 1;
382}
383
384四、循环队列完整模板
385
386#include <stdlib.h>
387
388#define MAXSIZE 100
389
390struct Queue {
391 int data[MAXSIZE];
392 int front;
393 int rear;
394};
395
396void InitQueue(struct Queue* q) {
397 q->front = 0;
398 q->rear = 0;
399}
400
401int IsEmpty(struct Queue* q) {
402 return q->front == q->rear;
403}
404
405int IsFull(struct Queue* q) {
406 return (q->rear + 1) % MAXSIZE == q->front;
407}
408
409int EnQueue(struct Queue* q, int x) {
410 if (IsFull(q)) {
411 return 0;
412 }
413
414 q->data[q->rear] = x;
415 q->rear = (q->rear + 1) % MAXSIZE;
416
417 return 1;
418}
419
420int DeQueue(struct Queue* q, int* x) {
421 if (IsEmpty(q)) {
422 return 0;
423 }
424
425 *x = q->data[q->front];
426 q->front = (q->front + 1) % MAXSIZE;
427
428 return 1;
429}
430
431int GetFront(struct Queue* q, int* x) {
432 if (IsEmpty(q)) {
433 return 0;
434 }
435
436 *x = q->data[q->front];
437 return 1;
438}
439
440五、链式队列完整模板
441#include <stdlib.h>
442
443struct Node {
444 int data;
445 struct Node* next;
446};
447
448struct LinkQueue {
449 struct Node* front;
450 struct Node* rear;
451};
452
453void InitQueue(struct LinkQueue* q) {
454 q->front = NULL;
455 q->rear = NULL;
456}
457
458int IsEmpty(struct LinkQueue* q) {
459 return q->front == NULL;
460}
461
462int EnQueue(struct LinkQueue* q, int x) {
463 struct Node* node = malloc(sizeof(struct Node));
464
465 if (node == NULL) {
466 return 0;
467 }
468
469 node->data = x;
470 node->next = NULL;
471
472 if (q->front == NULL) {
473 q->front = node;
474 q->rear = node;
475 } else {
476 q->rear->next = node;
477 q->rear = node;
478 }
479
480 return 1;
481}
482
483int DeQueue(struct LinkQueue* q, int* x) {
484 if (IsEmpty(q)) {
485 return 0;
486 }
487
488 struct Node* p = q->front;
489
490 *x = p->data;
491
492 q->front = p->next;
493
494 if (q->front == NULL) {
495 q->rear = NULL;
496 }
497
498 free(p);
499
500 return 1;
501}
502
503int GetFront(struct LinkQueue* q, int* x) {
504 if (IsEmpty(q)) {
505 return 0;
506 }
507
508 *x = q->front->data;
509 return 1;
510}
511
512六、易错点
5131. front 和 rear 的含义要固定
514
515front 指向队头元素
516rear 指向队尾后一个位置
517
518这个定义适合顺序循环队列。
519
5202. 循环队列判满不是 rear == MAXSIZE
521
522错误:q->rear == MAXSIZE
523
524正确:(q->rear + 1) % MAXSIZE == q->front
525
5263. 链式队列空队列时 front 和 rear 都要变 NULL
527
528出队时,如果删的是最后一个节点:
529
530if (q->front == NULL) {
531 q->rear = NULL;
532}
533
534否则 rear 会变成野指针。
535
5364. 链式队列入队要区分空队列和非空队列
537
538空队列:
539
540q->front = node;
541q->rear = node;
542
543非空队列:
544
545q->rear->next = node;
546q->rear = node;
547
5485. GetFront 不移动 front
549GetFront 只是看队头;
550DeQueue 才是真正删除队头。
551
552常见应用:
553BFS
554
555二叉树层序遍历
556
557图的广度优先搜索
558
559拓扑排序
560
561操作系统任务调度
562
563队列是“从队尾进,从队头出”的先进先出结构;数组实现要注意循环队列,链表实现要注意 front 和 rear 的维护。
队列 Queue ADT
- 前提条件
队列是一种线性表。
它的特点是:先进先出 FIFO
也就是:First In First Out
先进入队列的元素,先出队。
例如:
入队顺序:1 2 3
出队顺序:1 2 3
队列通常支持 EnQueue 入队、DeQueue 出队、GetFront 取队头、IsEmpty 判空等操作;队列一般可以用数组或链表实现。
- 核心概念
队列只允许:
队尾 rear 插入
队头 front 删除
图示:
front rear
↓ ↓
1 -> 2 -> 3 -> 4
操作规律:
入队:从 rear 进去
出队:从 front 出来
- ADT 操作定义
队列常见 ADT 操作:
InitQueue 初始化队列
IsEmpty 判断队列是否为空
EnQueue 入队
DeQueue 出队
GetFront 获取队头元素
DestroyQueue 销毁队列
一、顺序队列
- 前提条件
顺序队列就是:用数组实现队列
需要两个下标:
front:队头位置
rear:队尾后一个位置
2. 存储结构
#define MAXSIZE 100
struct Queue {
int data[MAXSIZE];
int front;
int rear;
};
这里:
data:存放元素
front:队头下标
rear:队尾后一个位置
3. 初始化
void InitQueue(struct Queue* q) {
q->front = 0;
q->rear = 0;
}
此时:
front == rear
表示队列为空。
-
判空
int IsEmpty(struct Queue* q) {
return q->front == q->rear;
}
-
入队
int EnQueue(struct Queue* q, int x) {
if (q->rear >= MAXSIZE) {
return 0;
}
q->data[q->rear] = x;
q->rear++;
return 1;
}
含义:
先把元素放到 rear 位置;
再让 rear 后移。
-
出队
int DeQueue(struct Queue* q, int* x) {
if (IsEmpty(q)) {
return 0;
}
*x = q->data[q->front];
q->front++;
return 1;
}
含义:
先取 front 位置元素;
再让 front 后移。
-
取队头
int GetFront(struct Queue* q, int* x) {
if (IsEmpty(q)) {
return 0;
}
*x = q->data[q->front];
return 1;
}
注意:
GetFront 只读取,不删除。
DeQueue 读取并删除。
- 顺序队列的问题
普通数组队列会有一个问题:假溢出
例如数组长度是 5:
下标:0 1 2 3 4
元素:1 2 3 4 5
出队两个:
下标:0 1 2 3 4
元素:_ _ 3 4 5
front = 2
rear = 5
虽然前面有空位,但:
rear == MAXSIZE
不能继续入队。
这就是顺序队列浪费空间的问题。
所以更常用的是:循环队列
二、循环队列
- 前提条件:循环队列用数组实现,但把数组看成一个环。
当 rear 到达数组末尾后,可以回到下标 0。
循环队列可以避免普通顺序队列前面出队后留下的空位不能继续使用的问题;数组循环队列通常可以让入队和出队都保持 O(1)。
- 存储结构
#define MAXSIZE 100
struct Queue {
int data[MAXSIZE];
int front;
int rear;
};
仍然是:
front:队头
rear:队尾后一个位置
3. 循环移动
普通后移:
q->rear++;
循环后移:
q->rear = (q->rear + 1) % MAXSIZE;
同理:
q->front = (q->front + 1) % MAXSIZE;
4. 初始化
void InitQueue(struct Queue* q) {
q->front = 0;
q->rear = 0;
}
5. 判空
int IsEmpty(struct Queue* q) {
return q->front == q->rear;
}
6. 判满
循环队列通常故意空出一个位置。
队满条件:
int IsFull(struct Queue* q) {
return (q->rear + 1) % MAXSIZE == q->front;
}
为什么要空一个位置?
因为如果不空位置:
front == rear
既可能表示空,也可能表示满。
所以用:
空一个位置
来区分空和满。
-
入队
int EnQueue(struct Queue* q, int x) {
if (IsFull(q)) {
return 0;
}
q->data[q->rear] = x;
q->rear = (q->rear + 1) % MAXSIZE;
//这里把rear++改成了循环到下一位
return 1;
}
-
出队
int DeQueue(struct Queue* q, int* x) {
if (IsEmpty(q)) {
return 0;
}
*x = q->data[q->front];
q->front = (q->front + 1) % MAXSIZE;
return 1;
}
-
取队头
int GetFront(struct Queue* q, int* x) {
if (IsEmpty(q)) {
return 0;
}
*x = q->data[q->front];
return 1;
}
-
循环队列核心
队空:
front == rear
队满:
(rear + 1) % MAXSIZE == front
入队:
data[rear] = x
rear = (rear + 1) % MAXSIZE
出队:
x = data[front]
front = (front + 1) % MAXSIZE
三、链式队列
- 前提条件
链式队列就是:用链表实现队列
需要两个指针:
front:指向队头结点
rear:指向队尾结点
队列可以用链表实现,链式队列通常更适合元素数量不固定的情况,因为它可以动态申请节点。
- 存储结构
struct Node {
int data;
struct Node* next;
};
struct LinkQueue {
struct Node* front;
struct Node* rear;
};
- 初始化
void InitQueue(struct LinkQueue* q) {
q->front = NULL;
q->rear = NULL;
}
空队列:
front = NULL
rear = NULL
-
判空
int IsEmpty(struct LinkQueue* q) {
return q->front == NULL;
}
-
入队
链式队列入队就是:尾插法
代码:
#include <stdlib.h>
int EnQueue(struct LinkQueue* q, int x) {
struct Node* node = malloc(sizeof(struct Node));
if (node == NULL) {
return 0;
}
node->data = x;
node->next = NULL;
//这里就是尾插法正常链表
//下面就是判断先让两者是否正常初始化,之后都指向node
if (q->front == NULL) {
q->front = node;
q->rear = node;
} else {
q->rear->next = node;
q->rear = node;
}
//这里是让rear指向的node的next连到新的node上去,然后移动新的node给rear
return 1;
}
6. 出队
链式队列出队就是:删除头结点
代码:
int DeQueue(struct LinkQueue* q, int* x) {
if (IsEmpty(q)) {
return 0;
}
struct Node* p = q->front;
*x = p->data;
q->front = p->next;
//就是把q的front位置data的值赋给x之后front再移动到下一个位置
if (q->front == NULL) {
q->rear = NULL;
}
free(p);
return 1;
}
注意:如果删完之后队列为空,
rear 也要置 NULL。
-
取队头
int GetFront(struct LinkQueue* q, int* x) {
if (IsEmpty(q)) {
return 0;
}
*x = q->front->data;
return 1;
}
四、循环队列完整模板
#include <stdlib.h>
#define MAXSIZE 100
struct Queue {
int data[MAXSIZE];
int front;
int rear;
};
void InitQueue(struct Queue* q) {
q->front = 0;
q->rear = 0;
}
int IsEmpty(struct Queue* q) {
return q->front == q->rear;
}
int IsFull(struct Queue* q) {
return (q->rear + 1) % MAXSIZE == q->front;
}
int EnQueue(struct Queue* q, int x) {
if (IsFull(q)) {
return 0;
}
q->data[q->rear] = x;
q->rear = (q->rear + 1) % MAXSIZE;
return 1;
}
int DeQueue(struct Queue* q, int* x) {
if (IsEmpty(q)) {
return 0;
}
*x = q->data[q->front];
q->front = (q->front + 1) % MAXSIZE;
return 1;
}
int GetFront(struct Queue* q, int* x) {
if (IsEmpty(q)) {
return 0;
}
*x = q->data[q->front];
return 1;
}
五、链式队列完整模板
#include <stdlib.h>
struct Node {
int data;
struct Node* next;
};
struct LinkQueue {
struct Node* front;
struct Node* rear;
};
void InitQueue(struct LinkQueue* q) {
q->front = NULL;
q->rear = NULL;
}
int IsEmpty(struct LinkQueue* q) {
return q->front == NULL;
}
int EnQueue(struct LinkQueue* q, int x) {
struct Node* node = malloc(sizeof(struct Node));
if (node == NULL) {
return 0;
}
node->data = x;
node->next = NULL;
if (q->front == NULL) {
q->front = node;
q->rear = node;
} else {
q->rear->next = node;
q->rear = node;
}
return 1;
}
int DeQueue(struct LinkQueue* q, int* x) {
if (IsEmpty(q)) {
return 0;
}
struct Node* p = q->front;
*x = p->data;
q->front = p->next;
if (q->front == NULL) {
q->rear = NULL;
}
free(p);
return 1;
}
int GetFront(struct LinkQueue* q, int* x) {
if (IsEmpty(q)) {
return 0;
}
*x = q->front->data;
return 1;
}
六、易错点
- front 和 rear 的含义要固定
front 指向队头元素
rear 指向队尾后一个位置
这个定义适合顺序循环队列。
- 循环队列判满不是 rear == MAXSIZE
错误:q->rear == MAXSIZE
正确:(q->rear + 1) % MAXSIZE == q->front
- 链式队列空队列时 front 和 rear 都要变 NULL
出队时,如果删的是最后一个节点:
if (q->front == NULL) {
q->rear = NULL;
}
否则 rear 会变成野指针。
- 链式队列入队要区分空队列和非空队列
空队列:
q->front = node;
q->rear = node;
非空队列:
q->rear->next = node;
q->rear = node;
- GetFront 不移动 front
GetFront 只是看队头;
DeQueue 才是真正删除队头。
常见应用:
BFS
二叉树层序遍历
图的广度优先搜索
拓扑排序
操作系统任务调度
队列是“从队尾进,从队头出”的先进先出结构;数组实现要注意循环队列,链表实现要注意 front 和 rear 的维护。