说起队列(queue),让人更为熟悉的一种表述则是FIFO(First In First Out)。正如其含义,先进先出,数据排队。
队列的官方定义是这么表述的:队列(queue)是只允许再一端进行插入操作,而在另一端进行删除操作的线性表。允许插入的一端称为队尾,允许删除的一端称为队头。
生活中有些时候会遇到一些队列操作的现象,比如你的电脑卡顿,点下鼠标的操作过一段时间才会执行,这是为什么呢?因为操作系统对于消息的基本处理机制是按照队列的方式入队,然后出队执行的。再比如银行服务叫号机制,取号等待,那也是一种以队列方式执行的排队模式。队列、堆栈,在数据结构中作为一种基本的模型,应用也颇多。
由定义知,队列是一种线性表,那么线性表的定义和使用有机会再说,需要注意的是线性表强调的是有限的序列,数据与数据之间像是被线串联在一起。线性表不代表一定是空间连续,因为线性表包含顺序表和链表。链表存储空间不一定是连续的,只要除了首尾元素外所有元素有唯一前驱和后继即可,且数据相“连”。
一、队列的基本编程表述
基本操作函数(Operation):
InitQueue(*Q); //初始化操作,一般是为队列开辟一个空间
DestroyQueue(*Q); //销毁队列,释放空间
ClearQueue(*Q); //清空队列数据,队列依然存在
QueueIsEmpty(Q);//判断队列是否为空
QueueIsFull(Q);//判断队列是否已满
GetHead(Q, *e);//获取队列首元素,即队头元素
EnQueue(*Q, e);//数据入队操作
DeQueue(*Q, *e);//数据出队操作
QueueLength(Q);//获取队列存储的数据长度
队列作为一种抽象的结构,不是说存储的数据只是一个int、char、float什么的数据,可以是结构体类型的数,包含各种类型数据的一个数据块。根据实际应用的需求,写出需要的函数即可,上面所列出来的操作只是说是常用的一些操作,未必全用,当然也可以自己再写一些其他的特殊操作,比如你想获取当前队列中某一个位置的元素,这些都是可以实现的。这些函数的命名也只是比较规范的一种总结,其实还有很多人用什么PopQueue()、PushQueue()….自己喜欢就好。
二、队列的基本编程实现
数组队列的实现:
数组实现队列是比较直接的一种方式,一般会定义一个结构体变量用于存储数组以及队头和队尾标识。
//队列存储的数据类型
typedef int QElemType;
//定义队列结构体
typedef struct
{
QElemType data[MAXSIZE];
int front;
int rear;
}SqQueue;
其中QElemType 是一个宏观意义上的类型,你可以定义其他结构体变量存入数组,笨拙的办法也可以不要这样的结构体,你知道队头队尾只是标识就好,用一个简单的数组,每次存入数据,将队尾标识加1即可,当然入队操作你要先判断队列(数组)是否已经存满。存满了要将数组中数据从第二个数据开始往前移动一个空间。覆盖队头数据,这样就可以持续存入了。出队的效果也差不多,出队其实就是用后面的数据移位覆盖队头数据,然后把队尾下标减1。这么一说,其实不难发现这样的移位操作是很不高效的,我们以循环移动front和rear标识来实现数据覆盖则显得更科学,即循环队列。循环队列中front和rear可以一直进行加1操作的原因是因为队列是线性存储,在已开辟的空间中地址是循环的,而不是表面意义上的数值增大。
循环队列的基本实现办法:
基本操作函数的实现
/*初始化操作,一般是为队列开辟一个空间*/
SqQueue * InitQueue(void)
{
SqQueue *Q;
Q = (SqQueue *)malloc(sizeof(SqQueue)); //开辟队列空间
//初始化队头队尾
Q->front = 0;
Q->rear = 0;
return Q;
}
/*获取队列存储的数据长度*/
unsigned char QueueLength(SqQueue *Q)
{
return (Q->rear - Q->front + MAXSIZE) % MAXSIZE; //循环队列取余操作
}
/*数据入队操作*/
void EnQueue(SqQueue *Q, QElemType e)
{
if (QueueLength(Q) != MAXSIZE - 1) //队列没有存满
{
Q->rear = (Q->rear + 1) % MAXSIZE; //首指针前移
Q->data[Q->rear] = e;
printf("%d", Q->data[Q->rear]);
}
else //队列存满循环覆盖
{
Q->front = (Q->front + 1) % MAXSIZE; //首指针前移
Q->rear = (Q->rear + 1) % MAXSIZE; //尾指针前移
Q->data[Q->rear] = e;
}
}
/*数据出队操作*/
void DeQueue(SqQueue *Q, QElemType *e)
{
//使用中应该判断队列是否为空
Q->front = (Q->front + 1) % MAXSIZE; //首指针前移
*e = Q->data[Q->front]
}
这张图是从网上找的,和代码不是很相合,代码实现过程中front所在的地址是不存储有效数据的。完整代码可留言,不过队列操作并不难,所以不放全代码了。函数的形式并不死板,按照实际需要可以做一些调整。