在上一篇博文中已经介绍了队列的链式存储结构,这里将介绍顺序存储结构
一,队列的定义
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
二,顺序结构分析
图1 入队列的操作
在图1 中可以看到入队列的操作其实就是在队尾追加一个元素,不需要任何移动,时间复杂度为o(1),出队列则不同,假设下标为0的位置是队列的队头,每次出队操作所有的元素就要向前移动。
图2 出队列的操作
可以看到出队列的操作,队头出了,剩下的人就需要都要往前移动一个位置,效率很低。就想现实中排队买火车票一样,前边的人买好了离开,后面的人就要全部向前一步补上空位。出队列的时间复杂度为o(n),效率很低。这个时候我们考虑一下出队列的操作能不能限制一下全体元素的移动。
图3 出队列的改进操作
如图3所示,这个时候将队头指针不设置为下标为0的元素,而是作为一个移动的指针,当下标为0的元素被出队列后,队头指针自动移动到下一个。但是这样可能会出现一个问题, 会出现一个数组越界的问题。
图4 数组越界的情况
图4所示,数组越界的问题就出现了,可事实上0和1的两个下标的位置还空着,这就叫假溢出。这个时候解决假溢出的方法就是如果后面满了,再重头开始,也就是头尾相接的循环。
循环队列:头尾相接的队列,就是循环队列。它的容量是固定的,并且它的队头和队尾指针都可以随着元素出入队列而改变,这样循环队列逻辑上就像是一个环形存储空间。但是要注意的是实际中没有真正的环形存储区,我们只是用来用顺序表模拟出来的逻辑上的循环。
图5 循环队列的示意图
在图5中可以看到,最开始队列的头指针与尾指针都是指向最开始的地方,后来a、b入队列,rear指针就指向第三个位置,接着a出队列,头指针就指向b,再接着c、d一次入队列,排在b后面,然后尾指针rear就指向了第一个位置,这样就实现了循环队列。似乎循环队列的实现只需要灵活地改变front和rear指针即可。也就是让front和rear指针不断加1,即使超出了地址范围,也会自动从头开始。我们就可以采取取模运算的模式:
(rear + 1) % QueueSize
(front + 1) % QueueSize
取模就是取余数的意思,他取到的值永远不会大于除数。
三,代码分析
1,定义循环队列
#define MAXSIZE 100
typedef struct
{
ElemType *base; //用于存放内存分配基地址
//这里你也可以用数组存放
int front;
int rear;
}
2,初始化一个队列
initQueue( cycleQueue *q)
{
q->base = (ElemType *)malloc(MAXSIZE * sizeof(ElemType));
if( !q->base)
exit(0);
q->front = q->rear = 0;
}
3,入队列操作
InsertQueue(cyleQueue *q, ElemType e)
{
if( (q->rear + 1)%MAXSIZE == q->front)
return; //队列已满
q->base[q->rear] = e;
q->rear = (q->rear+1)%MAXSIZE;
}
4,出队列操作
DeleteQueue(cycleQueue *q, ElemType *e)
{
if(q->front == q->rear)
return; //队列为空
*e = q->base[q->front];
q->front = (q->front+1)%MAXSIZE;
}