队列和栈都是一种运算受限的线性表。只允许在表的一端进行插入,而在另一端进行删除。允许删除的叫做队首(front),允许插入的叫做队尾(rear)。即先进先出原则,如同排队取票。
1. 非循环顺序队列
队列的顺序存储结构称为顺序队列。和顺序表一样,顺序队列,顺序队列也需要用一个数组存放当前队列中的元素。由于队头和队尾都是变化的,所以需要设置两个指针,分别指示当前队头元素和队尾元素在数组中的位置。
struct sequeue
{
datatype data[max_size];
int front,rear;
}; /*顺序队列的类型*/
sequeue *sq /*sq是顺序队列的指针*/
为方便起见,我们规定头指针front总是指向队头元素的前一个位置,尾指针指向当前队尾元素的位置,一开始队列的头指针和尾指针指向向量空间下界的前一个位置,在此设置为-1,若不考虑溢出,
- 则入队操作可表示为
sq->rear++; /*尾指针加1*/
sq->data[sq->rear]=element; /*元素element 入队*/
- 出队操作可表示为
sq->front++;
显然当 sq->front==sq->rear时,队列为空。队列为空时进行出队操作产生下溢,队满的条件是sq->rear-sq->front==maxsize,队满时进行入队操作就会导致上溢。
但是当前队列尾指针等于数组的上界即(sq->rear==maxsize-1)时依然会出现上溢。由此导致出现一种新现象即假上溢。因此需要在删除队首元素时将剩下的所有元素全部向队首迁移一位,由此又会导致队列删除元素时操作时间复杂度上升O(sq->rear-sq->front),由此引出一种新概念,循环队列。
2. 循环顺序队列
通常采用的方法是设想sq->data[maxsize]是一个首尾相接的圆环,即sq->data[0]接在sq->data[maxsize-1]之后,将这种队列称为循环队列。若当前尾指针等于数组的上界,再做入队操作时,令尾指针等于数组的下界,这样就能克服假上溢现象,因此循环列表下的尾指针加1可以表示为:
if(sq->rear+1>=maxsize)
{
sq->rear=0;
}
else
{
sq->rear++;
}
用模运算的情况下上述操作可以更间接地描述为
sq->rear = (sq->rear+1)%maxsize;
同样的出队列运算也可以用模运算来表示
sq->front = (sq->front+1)%maxsize;
若某元素出队列之后头指针追上尾指针,即sq->front ==sq->rear,则队列为空,若某元素进队列之后尾指针追上头指针即sq->rear ==sq->front,则队列满,因此sq->rear ==sq->front无法判断是否队空或者队满。
对此有两种解决办法:其一就是引入一个标识变量以区别队空或是队满,其二就是入队操作时检查尾指针加1之后会不会等于头指针即
if((sq->rear+1)%maxsize==sq->front;)
计算队列元素的个数的时有两种情况
1.当front < rear时,由于我们规定头指针front总是指向队头元素的前一个位置,尾指针指向当前队尾元素的位置,因此元素个数为rear-front;
2.当rear< front时,我们可以将整个队列分为两段分别是(rear-0+1)和(maxsize-1-front),即(maxsize+rear-front)如图所示
两者结合即有
(maxsize+rear-front)%maxsize