队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。队列是一种先进先出(First In Fitst Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为对头。队列的先进先出的结构正好符合日常生活中人们的生活习惯,即排在第一位的优先出栈,最后来的人自然排在队列最后了。
由于队列是特殊的线性表,所以也可以采用数组来顺序存储。假设一个队列有n个元素,则顺序存储的队列需要建立一个大于n的数组,并把队列的所有元素存储在数组的前n个单元,数组下标为0的一端即是对头,所谓的入队操作,其实就是在队尾追加一个元素,不需要移动任何元素,时间复杂度是O(1)。而出队时,是从下标为0的位置出队。那就意外着当执行出队操作时队列中的所有元素都得向前移动,以保证队列的对头仍然是下标为0的位置,此时时间复杂度是O(n)。其实,如果不去限制队列的元素必须存储在数组的前n个单元这一条件,出队的性能就会大大增加。这样的话,当执行出队操作时,不必所有的元素都向前移动,而只是让对头向后移动一位,指向新的队头。
为了避免当只有一个元素时,对头和队尾重合使得处理变得麻烦,所以引入两个指针,front指针指向对头元素,rear指针指向队尾元素的下一个位置,这样当front等于rear时,此队列不是还剩一个元素,而是空队列。可是这样做的话仍然有缺陷,假设长度为5的数组,初始状态时,front与rear指针均指向下标为0的位置,当执行多次入队操作后,rear指向了下标为4的位置,front指向着下标为0的位置。然后执行某次出队操作后,rear的位置不变,front指向了下标为2的位置。此时如果再执行入队操作时,rear会指向到下标为5的位置,也就是说rear移动到了数组之外。还有,如果这个队列的末尾位置已经被占用,此时如果再入队的话,就会产生数组越界的错误,可实际上,由于此队列执行过出队操作,其下标为0和下标为1的位置仍然空闲,此现象称为“假溢出”。
解决假溢出的最好方法就是当数组末尾被占用之后,就再从头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。以刚才的例子来说,当rear指向下标为4的位置时,再执行入队操作后,rear指向了下标为0的位置,元素成功插入到下标为4的位置。此时又有问题出现,当某次入队之后rear已经指向了front的位置,即rear和front重合了,此时就很难判断此队列是满队还是空队。解决方法是可以设置一个标识变量来判断,还有就是改变队满的条件,即保留一个元素空间。也就是说,当队列满时,数组中还会有一个空闲单元。这种判断队满的方法就是至今在使用的。此时队满的条件就是:(rear+1)%MAXSIZE==front(MAXSIZE为数组容量,%是为了整合rear与front的问题),通用的计算队列长度的公式为:(rear-front+MAXSIZE)%MAXSIZE。
采用循环队列来存储队列的一般操作有:入队、出队等。
1.入队。向队列中插入元素。
bool EnterQueue(SqQueue* s, ElemType e)
{
//如果队列满
if ((s->rear + 1) % MAXSIZE == s->front)
{
return false;
}
else
{
s->data[s->rear] = e; //将元素e赋值给队尾
s->rear = (s->rear + 1) % MAXSIZE; //rear指针向后移一位,若到最后则转到数组头部
return true;
}
}
2.出队。删除队中元素。
bool DeleteQueue(SqQueue* s, ElemType* e)
{
//队空
if (s->front == s->rear)
{
return false;
}
*e = s->data[s->front]; //将队头元素赋值给e
s->front = (s->front + 1) % MAXSIZE; //front指针向后移一位,若到最后则转到数组头部
return true;
}
3.求队列长度。
//求队列长度
int QueueLength(SqQueue* s)
{
return (s->rear - s->front + MAXSIZE) % MAXSIZE;
}
4.判空。
//判空
void QueueEmpty(SqQueue* s)
{
if (s->front == s->rear)
{
printf("队空!\n");
}
else
{
if ((s->rear + 1) % MAXSIZE == s->front)
{
printf("队满!\n");
}
else
{
printf("此队不为空!\n");
}
}
}
5.打印。
void ShowQueue(SqQueue* s)
{
for (int i = s->front; i < s->rear; i++)
{
printf("%d,", s->data[i]);
}
puts("\b;");
}
测试函数和声明:
//声明
typedef int ElemType; //队列容量
#define MAXSIZE 10
typedef struct
{
ElemType data[MAXSIZE];
int front; //头指针
int rear; //尾指针
}SqQueue;
//主函数
int main()
{
SqQueue s = { { 0 }, 0, 0 };
QueueEmpty(&s);
EnterQueue(&s, 0);
EnterQueue(&s, 1);
EnterQueue(&s, 2);
EnterQueue(&s, 3);
EnterQueue(&s, 4);
EnterQueue(&s, 5);
EnterQueue(&s, 6);
QueueEmpty(&s);
ShowQueue(&s);
printf("%d\n", QueueLength(&s));
EnterQueue(&s, 7);
EnterQueue(&s, 8);
QueueEmpty(&s);
ShowQueue(&s);
printf("%d\n", QueueLength(&s));
return 0;
}
以上即是与队列的顺序存储相关的~~