队列是一种简单的先进先出结构,各种需要排队的事情,都可以开一个队列来完成。
利用链表或数组,都能实现队列,不过最大的区别就是,数组的扩展比较困难,而链表较为容易,但链表资源消耗稍多。
数据结构的不同导致了队列的实现也不相同,链表上次已经实现过了,只需简单包装即可使用,这里,我们介绍简单的用数组模拟队列的方式:
这个队列是固定长度的一个数组构建的,另外保存两个int数字,负责记录数组的下标索引。
我们下面就来编写一下这个队列,还是使用C语言,在此,我会继续介绍C语言的基础知识。
队列实现
复习一下上次的知识,C语言定义基本结构和宏定义,我们也先对队列进行必要的定义
/* queue.h */
#ifndef QUEUE_H
#define QUEUE_H
#include "malloc.h"
typedef char bool;
typedef int QueueElementType;
#define QueueSize 200
typedef struct _queue
{
int head, tail;
QueueElementType data_array[QueueSize];
} queue;
#endif // QUEUE_H
注意这里,我们提前设置了队列的长度,对于不知道队列具体应该多长的问题,可能并不大合适,不过过后我们会介绍如何计算队列需要的最大长度。
依旧是定义四个函数:
/* 队列的构造函数 */
queue* QueueCreate();
/* 从结尾压入元素 */
void QueuePush(queue* q, QueueElementType data);
/* 从头部弹出元素 */
QueueElementType QueuePop(queue* q);
/* 判断队列是否为空 */
bool QueueIsEmpty(queue* q);
比较简单的构造函数和判断函数这里直接给出,注意初始化的条件是头尾指针皆为0:
queue* QueueCreate() {
queue* q = (queue*) malloc(sizeof(queue));
q->head = 0;
q->tail = 0;
return q;
}
bool QueueIsEmpty(queue* q) {
return q->head == q->tail;
}
至于压入弹出也很简单,但需要注意的是,为了减少内存消耗,我们希望这个队列是循环队列。
(PS. 当年的唐山一中TSOI,有一句经典搞笑语录,“队列是先进先出的,循环队列是进进出出的。” 哎,跑题了,大家无视无视。)
至于什么是循环队列,大家请看下面这张图:
恩,通过模运算就让队列循环运动,很方便的设计,不过还是要注意,永远不要让tail追上head,那时,队列就溢出了。
所以,我们的操作函数Push和Pop是这样的:
void QueuePush(queue* q, QueueElementType data) {
q->data_array[ q->tail ] = data;
q->tail = ++(q->tail) % QueueSize;
}
QueueElementType QueuePop(queue* q) {
if (QueueIsEmpty(q)) return (QueueElementType)NULL;
QueueElementType data = q->data_array[ q->head ];
q->head = ++(q->head) % QueueSize;
return data;
}
队列的使用当然十分简单,只需要操作这四个函数即可:
#include <stdio.h>
#include "queue.h"
queue* q = NULL;
int main() {
q = QueueCreate();
QueuePush(q, 10);
QueuePush(q, 20);
QueuePush(q, 30);
printf("%d\n", QueuePop(q));
printf("%d\n", QueuePop(q));
printf("%d\n", QueuePop(q));
return 0;
}
完整的代码详见附录,这里就不再具体多说了。
队列的应用
说了队列的写法,那么应该介绍一下,队列都有哪些用途。广搜问题,是让我最早认识队列的问题,这里就给大家举一个迷路小孩回家问题吧= =,画着X的地方表示无法通行,给他找一条可行的路径。
开一个队列,记录已经走的步数和当前的位置,然后根据地图结构,每次看队列新弹出的元素的位置,分别向四个方向搜索,如果已经搜索过的就不要找了,整个搜索过程就像漫水一样,这样就能找到可行的路径了。
怎么样,这就是搜索问题中经典的广度优先遍历,是不是也很简单呢?