目录
队列是一种只允许一端进行插入操作,一端只允许进行删除操作的线性表。
队列是一种先进先出(FIFO)的线性表,它只允许在表的一端进行插入和在表的另一端删除元素。在队列中,允许插入的一段叫做队尾(rear),允许删除的一端叫做队头(front)。假设队列为q = (a1,a2,a3......an)。那么a1即为队头元素,an为队尾元素。如图1所示:
和线性表线性表相似,队列也有顺序存储和链式存储。
顺序队列通常采用一维数组进行存储。通常设队头指针指向队列的第一个元素,队尾指针指向队尾元素的后一个位置。front和rear的初值在队列初始化时均应置为0;入队时将新元素插入rear所指的位置,然后将rear加1。出队时,删去front所指的元素,然后将front加1并返回被删元素:由此可见,当头尾指针相等时队列为空。在非空队列里,头指针始终指向队头元素,而尾指针始终指向队尾元素的下一个位置。如图2所示:
但是若以这样的形式存储,会出现一个极端状况,如图3所示:
当接着进行入队操作时,发现rear会数组越界,表示不能存储,但是a4前面还有许多空间没有利用,这无疑是对资源的浪费,而这种现象被称之为“假溢出”,为了解决资源浪费,为此设计了循环队列。
所谓的循环队列其实也是采用一维数组进行存储的,但是我们应将之想象为该一维数组首尾相接,如图4所示结构:
入队:入队时将新元素插入rear所指的位置,然后将rear加1;
出队:删去front所指的元素,然后将front加1并返回被删元素;
假设一种情况:rear指针此时指向下标7的位置,front指针指向下标3的位置,如图5所示:
此时添加元素a7,那么rear指针将指向哪里呢?????
答案时这样的:由于数组0,1,2下标均没有元素,故可以将a7插入下标为7的位置,然后将rear指针指向下标0处,这样便解决了“假溢出”的问题。
那么如何计算front,rear的下标位置呢????
入队:rear = (rear + 1) % MAXSIZE;
出队: front = (front + 1) % MAXSIZE;
前面我们知道,在顺序队列中,当front = rear时,队列为空,如图6所示:
此时front = rear,但是此时队列为满,为此我们提出一个约定,约定内容为,整个队列中,空出一个位置作为标记,当rear在front后面并且相邻,则说明队列已满,如图7所示:
具体表示方法为:
判空: rear == front;
判满:(rear + 1) % MAXSIZE = front;
获取长度:(rear - front + MAXSIZE) % MAXSIZE;
基本操作:
- //创建循环队列
Status InitQueue(SqQueue &Q); - //销毁队列
Status DestoryQueue(SqQueue &Q); - //判断队列是否为满
bool QueueFull(SqQueue Q); - //判断队列是否为空
bool QueueEmpty(SqQueue Q); - //清空队列
Status ClearQueue(SqQueue &Q); - //获取队列长度
int QueueLength(SqQueue Q); - //用e返回队头元素
Status GetHead(SqQueue Q,QElemType &e); - //插入元素,e为Q的新的队尾元素
Status EnQueue(SqQueue &Q,QElemType e) ; - //删除Q的队头元素,并用e返回其值
Status DeQueue(SqQueue &Q,QElemType &e) ; - //从队尾到队头,依次对Q的每个数据元素调用函数visit().输出队列元素
Status QueueTraverse(SqQueue Q,Status (*visit)(QElemType e));
代码实现:
#include<stdio.h>
#include<malloc.h>
#define MAXSIZE 100 //最大队列元素
enum Status{OK = 1,ERROR = 0};
typedef int QElemType;
typedef struct
{
QElemType *base; //初始化的动态分配存储空间
int front; //头指针,若队列不空,指向队列头元素
int rear; //尾指针,若队列不空,指向队列尾元素的下一位置
}SqQueue;
//创建循环队列
Status InitQueue(SqQueue &Q)
{
//动态开辟一个空队列Q
Q.base = (QElemType*)malloc(MAXSIZE * sizeof(QElemType));
if(NULL == Q.base)
return ERROR;
Q.front = Q.rear = 0;
return OK;
}
Status DestoryQueue(SqQueue &Q)
{
if(Q.base)
free(Q.base); //动态释放
Q.front = Q.rear = 0;
return OK;
}
//判断队列是否为满
bool QueueFull(SqQueue Q)
{
return (Q.rear + 1) % MAXSIZE == Q.front;
}
//判断队列是否为空
bool QueueEmpty(SqQueue Q)
{
return Q.rear == Q.front;
}
//清空队列
Status ClearQueue(SqQueue &Q)
{
Q.front = Q.rear;
return OK;
}
//获取队列长度
int QueueLength(SqQueue Q)
{
return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
//用e返回队头元素
Status GetHead(SqQueue Q,QElemType &e)
{
if(QueueEmpty(Q))
return ERROR;
e = Q.base[Q.front];
return OK;
}
//插入元素,e为Q的新的队尾元素
Status EnQueue(SqQueue &Q,QElemType e)
{
if(QueueFull(Q))
return ERROR;
Q.base[Q.rear] = e;
Q.rear = (Q.rear + 1) % MAXSIZE;
return OK;
}
//删除Q的队头元素,并用e返回其值
Status DeQueue(SqQueue &Q,QElemType &e)
{
if(QueueEmpty(Q))
return ERROR;
e = Q.base[Q.front];
Q.front = (Q.front + 1) % MAXSIZE;
return OK;
}
//定义visit()
Status PrintElement(QElemType e)
{
printf("%d ",e);
return OK;
}
//从队尾到队头,依次对Q的每个数据元素调用函数visit().输出队列元素
Status QueueTraverse(SqQueue Q,Status (*visit)(QElemType e))
{
while(!QueueEmpty(Q))
{
(*visit)(Q.base[Q.front]);
Q.front = (Q.front + 1) % MAXSIZE;
}
printf("\n");
return OK;
}
main函数:
int main()
{
SqQueue queue;
int e;
InitQueue(queue);
EnQueue(queue,2);
EnQueue(queue,4);
EnQueue(queue,6);
EnQueue(queue,11);
QueueTraverse(queue,PrintElement);
DeQueue(queue,e);
QueueTraverse(queue,PrintElement);
printf("len = %d\n",QueueLength(queue));
ClearQueue(queue);
QueueTraverse(queue,PrintElement);
EnQueue(queue,11);
QueueTraverse(queue,PrintElement);
GetHead(queue,e);
printf("head = %d\n",e);
DestoryQueue(queue);
return 0;
}
所谓链式队列,其实就是一个单链表,只不过对其插入和删除做出了限制;允许插入的一端作为队头(front),允许删除的一端作为队尾(rear);具体结构如图8所示:
此时front指向头结点,rear指向终端节点。
当队列为空队列时,front和rear均指向头结点。如图9所示:
对于删除则存在一个极端情况:如图10所示:
对于一般情况,删除队列头元素仅需修改头结点中的指针(front->next = p->next),但是当队列中只有最后一个元素时,这样做便会使rear指针丢失,因此需要对队尾指针重新赋值,即指向头节点(if(rear == p) rear = front)。
基本操作:
//构造一个空队列
Status InitQueue(LinkQueue &Q);
//销毁队列Q,Q不在存在
Status DestroyQueue(LinkQueue &Q);
//将Q清为空队列
Status ClearQueue(LinkQueue &Q);
//若队列为空队列,则返回TRUE,否则返回FALSE
int QueueLength(LinkQueue Q);
//若队列不为空,则用e返回Q的队头元素,并返回OK,否则返回ERROR
Status GetHead(LinkQueue Q,QElemType &e);
//插入元素e为Q的新的队尾元素
Status EnQueue(LinkQueue Q,QElemType e);
//若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回FALSE
Status DeQueue(LinkQueue Q,QElemType &e);
代码实现:
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
enum Status{OK = 1,ERROR = 0,TRUE = 1,FALSE = 0,OVERFLOW = -1};
typedef int QElemType;
typedef struct Node //结点域
{
QElemType data;
struct Node* next;
}Node,*QueuePtr;
typedef struct
{
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
//构造一个空队列
Status InitQueue(LinkQueue &Q)
{
Q.front = Q.rear = (QueuePtr)malloc(sizeof(Node));
if(NULL == Q.front)
exit(OVERFLOW);
Q.front->next = NULL;
return OK;
}
//销毁队列Q,Q不在存在
Status DestroyQueue(LinkQueue &Q)
{
while(Q.front != NULL)
{
Q.rear = Q.front->next;
free(Q.front);
Q.front = Q.rear;
}
Q.front = NULL;
return OK;
}
//将Q清为空队列
Status ClearQueue(LinkQueue &Q)
{
QueuePtr p;
while(Q.front->next != NULL)
{
p = Q.front->next;
Q.front->next = p->next;
free(p);
}
Q.rear = Q.front;
return OK;
}
//若队列为空队列,则返回TRUE,否则返回FALSE
Status QueueEmpty(LinkQueue Q)
{
if(Q.front == Q.rear)
return TRUE;
else
return FALSE;
}
//获取队列长度
int QueueLength(LinkQueue Q)
{
int count = 0; //定义一个计数器
QueuePtr p = Q.front;
while(p->next != NULL)
{
count++;
p = p->next;
}
return count;
}
//若队列不为空,则用e返回Q的队头元素,并返回OK,否则返回ERROR
Status GetHead(LinkQueue Q,QElemType &e)
{
if(Q.front == Q.rear)
return ERROR;
e = Q.front->next->data;
return OK;
}
//获取结点
static QueuePtr GetNode(QElemType e)
{
Node *pNode = (QueuePtr)malloc(sizeof(Node));
if(NULL == pNode)
exit(OVERFLOW);
pNode->data = e;
pNode->next = NULL;
return pNode;
}
//插入元素e为Q的新的队尾元素
Status EnQueue(LinkQueue &Q,QElemType e)
{
Node *pNode = GetNode(e);
if(NULL == pNode)
exit(OVERFLOW);
Q.rear->next = pNode;
Q.rear = pNode;
return OK;
}
//若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回FALSE
Status DeQueue(LinkQueue Q,QElemType &e)
{
if(Q.front == Q.rear)
return ERROR; //说明此时队列为空
QueuePtr p = Q.front->next;
e = p->data;
Q.front->next = p->next;
if(Q.rear == p)
Q.rear = Q.front;
free(p);
return OK;
}
//定义visit()
Status PrintElement(QElemType e)
{
printf("%d ",e);
return OK;
}
//从队头到队尾以此对队列Q中的每个元素调用函数visit(),一旦visit失败
Status QueueTraverse(LinkQueue Q,Status (*visit)(QElemType e))
{
QueuePtr p = Q.front->next;
while(p != NULL)
{
(*visit)(p->data);
p = p->next;
}
printf("\n");
return OK;
}
main函数:
int main()
{
LinkQueue queue;
int e = 0;
InitQueue(queue);
EnQueue(queue,2);
EnQueue(queue,6);
EnQueue(queue,8);
EnQueue(queue,12);
EnQueue(queue,45);
QueueTraverse(queue,PrintElement);
DeQueue(queue,e);
DeQueue(queue,e);
QueueTraverse(queue,PrintElement);
EnQueue(queue,25);
printf("len = %d\n",QueueLength(queue));
ClearQueue(queue);
printf("len = %d\n",QueueLength(queue));
EnQueue(queue,12);
EnQueue(queue,26);
QueueTraverse(queue,PrintElement);
printf("len = %d\n",QueueLength(queue));
GetHead(queue,e);
printf("head = %d\n",e);
DestroyQueue(queue);
return 0;
}
总结:
队列其实就是线性表的特例化,它只允许在队头插入元素,在队尾删除元素,其出队入队操作时间复杂度均为O(1)。