队列的基本概念
队列简称队,队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头,如下图:
【注】1.队列的操作特性为先进先出(First In First Out,FIFO);
2.不含任何元素的空表为空队列
像栈一样,队列也是有顺序和链式两种存储结构的。
队列的顺序存储结构
顺序队列
1.顺序队列的实现
队列的顺序操作实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针,队头指针 front 指向队头元素,队尾指针指向队尾元素的下一个位置。
队列的顺序存储类型可描述为:
#define MaxSize 50 //定义队列中元素的最大个数
typedef struct{
ElemType data[MaxSize]; //存放队列元素
int front, rear; //队头和队尾指针
}SqQueue;
初始状态(队空条件):Q.front == Q.rear == 0;
进队操作:队不满时,先送值到队尾元素,再将队尾指针加 1;
出队操作:队不空时,先取对头元素值,再将队头指针加 1;
2.顺序队列中的溢出现象
(1) "下溢"现象:当队列为空时,做出队运算产生的溢出现象。“下溢”是正常现象,常用作程序控制转移的条件。
(2)"真上溢"现象:当队列满时,做进栈运算产生空间溢出的现象。“真上溢”是一种出错状态,应设法避免。
(3)"假上溢"现象:由于入队和出队操作中,头尾指针只增加不减小,致使被删元素的空间永远无法重新利用。当队列中实际的元素个数远远小于向量空间的规模时,也可能由于尾指针已超越向量空间的上界而不能做入队操作。该现象称为"假上溢"现象。
循环队列
为了避免顺序队列溢出的缺点,引出了循环队列,我们可以将循环队列假想为一个环状的空间;
1.初始化队列
初始状态(队空条件):Q.froint == Q.rear ;
bool InitQueue(SqQueue *Q){
Q->front =0;
Q->rear =0;
return true;
}
2.判断队列是否为空
队列为空时:Q.front == Q.rear;
bool QueueEmpty(SqQueue Q){
if(Q.front == Q.rear)
return true;
else
return false;
}
3.进队列
bool EnQueue(SqQueue *Q, ElemType e){
if((Q->rear+1)%MaxSize == Q->front) //队列已满
return false;
Q->data[Q->rear] = e; //插入队尾
Q->rear = (Q->rear +1)%MaxSize; //尾部指针后移,如果到最后则转到头部
return true;
}
4.出队列
bool DeQueue(SqQueue *Q, ElemType *e){
if(Q->front == Q->rear) //队列空
return false;
*e = Q->data[Q->front]; //返回队头元素
Q->front = (Q->front+1)%MaxSize; //队头指针后移,如到最后转到头部
return true;
}
5.清空队列
bool ClearQueue(SqQueue *Q){
Q->front = Q->rear =0;
return true;
}
6.返回队列中元素个数
int QueueLength(SqQueue Q){
return (Q.rear-Q.front+MaxSize)%MaxSize;
}
【注】各操作图示:
队列的链式存储结构
1.链式队列
队列的链式表示称为链队列,它实际上时同时带有队头指针和队尾指针的单链表,头指针指向对头结点,尾指针指向队尾结点,即单链表的最后一个结点,如下图:
队列的链式存储类型可描述为:
//链式队列结点
typedef struct{
ElemType data;
struct LinkNode *next;
}LinkNode;
//链式队列
typedef struct{
LinkNode *front, *rear; //链式队列的头尾指针
}LinkQueue;
当Q.front == NULL 且 Q.rear == NULL 时,链式队列为空;
不带头结点的链式操作往往是比较麻烦的,因此我这里的分析是基于带头结点的单链表,这样的话,插入和删除操作就统一了;
2.初始化
void InitQueue(LinkQueue &Q){
Q.front = Q.rear = (LinkQueue*)malloc(sizeof (LinkQueue)); //建立头结点
Q.front->next = NULL; //初始化为空
}
3.判队空
bool IsEmpty(LinkQueue &Q){
if(Q.front == Q.rear) //队空
return true;
else
return false; //非空
}
4.入队列
void EnQueue(LinkQueue &Q, ElemType x){
LinkNode *s = (LinkNode *)malloc(sizeof (LinkNode)); //创建新结点
s->data = x;
s->next = NUll; //插入到链尾
Q.rear->next = s;
Q.rear = s; //队尾指针后移
}
5.出队列
bool DeQueue(LinkNode &Q, ElemType &x){
if(Q.front == Q.rear) //列已空
return false;
LinkNode *p = Q.front->next; //p指向第一个结点
x = p->next;
Q.front->next = p->next; //删除p结点
if(Q.rear == p)
Q.rear = Q.front; //若原队列只有一个结点,则删除后变空
free(p);
return true;
}
【注】用单链表表示的链式队列特别适合数据元素变动比较多的情形,而且不会存在队列满且差生溢出的问题。另外,假如程序中要用到多喝队列,与多个栈的情形一样,最好使用链式队列,这样就不会出现存储分配不合理和“溢出”的问题了。