一、循环队列
a、概念
为充分利用向量空间,克服"假溢出"现象的方法是:将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量。存储在其中的队列称为循环队列(Circular Queue)。
通过上图可以看出,如果使用顺序表作为队列的话,当处于d状态则不能继续插入新的队尾元素,否则会因为数组越界而导致程序代码被破坏。
所以产生了由链表实现的循环队列,只有队列未满时才可以插入新的队尾元素。判断循环队列是空队列还是满队列依据:头尾指针相同就是空,尾指针的下一个是头指针就是满。
b、代码及运行结果
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define MAXQSIZE 5
typedef int Status;
typedef int QElemType;
typedef struct Node
{
QElemType *base; //初始化动态分配存储空间
int front;
int rear;
} SqQueue;
Status InitQueue(SqQueue *Q)
{
Q->base = (QElemType *)malloc(MAXQSIZE * sizeof(QElemType));
if (!Q->base)
return ERROR;
Q->front = Q->rear = 0;
return OK;
}
Status EnQueue(SqQueue *Q, QElemType elem)
{
//队列为空时 1%5==1,队列满时(4+1)%5==0,最多容纳4个元素
if ((Q->rear + 1) % MAXQSIZE == (Q->front))
return ERROR;
Q->base[Q->rear] = elem;
Q->rear = (Q->rear + 1) % MAXQSIZE; //rear始终在0-5中循环
return OK;
}
Status OutQueue(SqQueue *Q, QElemType *e)
{
if (Q->front == Q->rear)
return ERROR;
*e = Q->base[Q->front];
Q->front = (Q->front + 1) % MAXQSIZE;
return OK;
}
Status PrintQueue(SqQueue Q)
{
printf("the queue is:");
for (int i = Q.front; i < Q.rear; ++i)
printf("%d ", Q.base[i]);
return OK;
}
int main()
{
SqQueue queue;
QElemType elem;
int i;
while(!InitQueue(&queue));
EnQueue(&queue,1);
EnQueue(&queue,2);
EnQueue(&queue,3);
EnQueue(&queue,4);
EnQueue(&queue,5);//队列已满,无效入队
printf("\noutput:");
scanf("%d", &i);
while(i != 0)
{
OutQueue(&queue, &elem);
i--;
}
PrintQueue(queue);
return OK;
}
二、链队列
a、概念
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。
当队列为空时,front和rear都指向头结点;
入队:在队列尾部插入结点;
出队:头结点的后继结点(队头)出队,将头结点的后继改为他后面的结点,若链表除头结点外只剩一个元素时,则需将rear指向头结点。
一般情况下,链队列的出队图示:
b、代码及运行结果
#include<stdio.h>
#include<stdlib.h>
//链队列结点结构
typedef struct QNode{
int data;
struct QNode *next;
}QNode;
//链队列结构
typedef struct LiQueue{
QNode *front;
QNode *rear;
}LiQueue;
//创建链队列
LiQueue initQueue(){
LiQueue *lq=(LiQueue *)malloc(sizeof(LiQueue));
if(lq ==NULL){
return *lq;
}
lq->front=lq->rear=NULL;
return *lq;
}
//判断链队列是否为空
int isEmpty(LiQueue *lq){
if(lq->front==NULL || lq->rear==NULL){
return 0;
}else{
return 1;
}
}
//元素进链队列
void enQueue(LiQueue *lq,int x){
QNode *p;
p=(QNode *)malloc(sizeof(QNode));
p->data=x;
p->next=NULL;
if(lq->rear==NULL){
lq->front=lq->rear=p;//如果第一次插入则设置头指针和尾指针为p
}else{
lq->rear->next=p;//链队列的尾部插入p
lq->rear=p;//设置链队列的尾指针指向p
}
}
//元素出链队列
int deQueue(LiQueue *lq,int *y){
QNode *p=lq->front;
if(lq->rear==NULL || lq->front==NULL){
return 0;
}
if(lq->front==lq->rear){
lq->front=lq->rear=NULL;
}else{
lq->front=lq->front->next;
}
*y=p->data;
free(p);
return 1;
}
//打印链队列
void printQueue(LiQueue lq){
if(lq.front==NULL || lq.rear==NULL){
return;
}
while(lq.front!=NULL){
printf("%d\n",lq.front->data);
lq.front=lq.front->next;
}
}
int main(void){
int y=0;
LiQueue lq=initQueue();
enQueue(&lq,1);
enQueue(&lq,2);
enQueue(&lq,3);
enQueue(&lq,4);
enQueue(&lq,5);
//deQueue(&lq,&y);
printQueue(lq);
printf("出队列元素=%d\n",y);
return 0;
}
三、循环队列和链队列的比较
从时间上,其实它们的基本操作都是常数时间,即都为0(1)的,不过循环队列是事先申请好空间,使用期间不释放,而对于链队列,每次申请和释放结点也会存在一些时间开销,如果入队出队频繁,则两者还是有细微差异。
对于空间上来说,循环队列必须有一个固定的长度,所以就有了存储元素个数和空间浪费的问题。而链队列不存在这个问题,尽管他需要一个指针域,会产生一些空间上的开销,但也可以接受。所以在空间上,链队列更加灵活。
总的来说,在可以确定队列长度的情况下,建议用循环队列;不能预估队列长度时,建议用链队列。