1 队列的基本概念
队列是另一种特殊的表,这种表只在表首(队首)进行删除操作,只在表尾(为队尾)进行插入操作。由于队列的修改是按先进先出的规则进行的,所以队列又称为先进先出(First In First Out) 表, 简称FIFO表。
假设队列为a(1),a(2)……a(n),那么a(1)就是队首元素,a(n)为队尾元素。队列中的元素是按a(1),a(2)……a(n)的顺序进入的,退出队列也只能按照这个次序依次退出。也就是说,只有在a(1)离开队列之后,a(2)才能退出队列。只有在a(1),a(2)……a(n-1)都离开队列之后,a(n)才能退出队列。下图为队列示意图。
队列支持的6个基本运算如下。
(1) QueueEmpty(Q) :测试队列是否为空。
(2) QueueFull(Q) :测试队列Q是否已满。
(3) QueueFirst(Q) :返回队列的队首元素。
(4) QueueLast(Q) :返回队列Q的队尾元素。
(5) EnterQueue(x,Q) :在队列Q的队尾插入元素x。
(6) DeleteQueue(Q) :删除并返回队列Q的队首元素。
2 用指针实现队列
任何一种实现表的方法都可以用于实现队列。用指针实现队列实际上得到一个单链表。队列结点的类型与单链表结点类型相同。
typedef struct qnode *qlink;//队列结点指针类型
typedef qnode{//队列结点
Qitem element;//队列元素
qlink next;//指向下一结点的指针
}Qnode;
2.1 队列的类型由QItem定义
此处队列元素的类型定义为int。
typedef int QItem;//队列元素类型
typedef QItem *Qaddr;//队列元素指针类型
void QItemShow(QItem x)
{//输出队列元素
printf("%d",x);
}
2.2 函数NewQNode()
函数NewQNode()产生一个新队列结点。
qlink NewQNode()
{
return (qlink)malloc(sizeof(Qnode));
}
2.3 用指针实现的队列Queue
定义如下:
typedef struct lque *Queue;//队列指针类型
typedef struct lque{//队列结构
qlink front;//队首指针
qlink rear;//对尾指针
}Lqueue;
由于入队的新元素是在队尾进行插入的,所以用一个指针rear来指示队尾,可以使入队运算不必从头到尾检查整个表,从而提高运算的效率。
2.4 函数QueueInit()
函数QueueInit()将队首指针front和队尾指针rear置为空指针,创建一个空队列。
Queue QueueInit()
{
Queue Q=(Queue)malloc(sizeof *Q);
Q->front=Q->rear=0;
return Q;
}
2.5 函数QueueEmpty()
坚持front是否为空指针。
int QueueEmpty(Queue Q)
{
return Q->front==0;
}
2.6 函数QueueFirst()
返回队列Q队首结点中的元素。
QItem QueueFirst(Queue Q)
{//前提队列非空
if(QueuEmpty(Q)) exit(1);
return Q->queue[(Q->front+1)%Q-?maxsize];
}
2.7 函数QueueLast()
返回队列Q的队尾结点的元素。
QItem QueueLast(Queue Q)
{//前提队列非空
if(QueuEmpty(Q)) exit(1);
return Q->queue[Q->rear];
}
2.8 函数EnterQueue(Q)
先为元素x创建一个新结点,然后修改队列Q的队尾结点指针,在队尾插入新结点,使新结点成为新队尾结点。
void EnterQueue(QItem x,Queue Q)
{
qlink p=NewQNode();//创建一个新结点
p->element=x;
p->next=0;
//队尾插入新结点
if(Q->front) Q->rear->next=p;//队列非空
else Q->front=p;//空队列
Q->rear=p;
}
2.8 函数DeleteQueue(Q)
先将队首元素存于x中,然后修改队列Q的队首结点指针使其指向队首结点的下一个结点,从而删除队首结点,最后返回x。
QItem DeleteQueue(Q)
{//前提队列非空
if(QueuEmpty(Q)) exit(1);
QItem x=Q->front_>element;//将队首元素存于x中
//删除队首结点
qlink p=Q->front;
Q->front =Q->front->next;
free(p);
return x;
}
用指针实现队列基本运算都只要O(1)计算时间。
3 用循环数组实现队列
3.1 什么是循环数组
queue[0:maxsize-1]中的单元不是排成一行,而是围成一个圆环,即queue[0]接在queue[maxsize-1]的后面,这就是循环数组。
3.2 3种方式指示队首与对尾元素
用循环数组实现队列时,将队列中从队首到队尾的元素按顺时针方向存放在循环数组的一段连续的单元中。当新元素入队时,可将队尾游标rear按顺时针方向移一位,并在这个单元中存入新元素。出队只要将队首游标front按顺时针方向移一位即可。
抽象方式指示队首与对尾元素
一,直接用队首游标front指向队首元素所在的单元,用队尾游标rear指向队尾元素所在的单元,如下图。
二,用队首游标front指向队首元素所在的单元的前一个单元,用队尾游标rear指向队尾元素所在的单元,如下图。
三,用队首游标front指向队首元素所在的单元,用队尾游标rear指向队尾元素所在的单元的下一个单元,如下图。
具体方式指示队首与对尾元素
一,直接用队首游标front指向队首元素所在的单元,用队尾游标rear指向队尾元素所在的单元,如下图。
二,用队首游标front指向队首元素所在的单元的前一个单元,用队尾游标rear指向队尾元素所在的单元,如下图。
三,用队首游标front指向队首元素所在的单元,用队尾游标rear指向队尾元素所在的单元的下一个单元,如下图。
3.3 满队列
一,直接用队首游标front指向队首元素所在的单元,用队尾游标rear指向队尾元素所在的单元,如下图。
二,用队首游标front指向队首元素所在的单元的前一个单元,用队尾游标rear指向队尾元素所在的单元,如下图。
三,用队首游标front指向队首元素所在的单元,用队尾游标rear指向队尾元素所在的单元的下一个单元,如下图。
3.4 空队列
一,直接用队首游标front指向队首元素所在的单元,用队尾游标rear指向队尾元素所在的单元,如下图。
二,用队首游标front指向队首元素所在的单元的前一个单元,用队尾游标rear指向队尾元素所在的单元,如下图。
三,用队首游标front指向队首元素所在的单元,用队尾游标rear指向队尾元素所在的单元的下一个单元,如下图。
有两种处理方法来解决任何表示满队列和空队列这个问题。其一是另设一个布尔量来注明队列是空还是满。其二,约定当循环数组中元素个数达到maxsize-1时队列为满。
采用直接用队首游标front指向队首元素所在的单元,用队尾游标rear指向队尾元素所在的单元的方法,并用约定当循环数组中元素个数达到maxsize-1时队列为满的方法区分满队列和空队列。
3.5 用循环数组实现的队列Queue
思路:定义队列指针类型,队列结构,队首游标,队尾游标,循环数组。
typedef srtuct aque *Queue;//队列指针类型
typedef struct aque{//队列结构
int maxsize;//循环数组大小
int front;//队首游标
int rear;//对尾游标
QItem *queue;//循环数组
}Aqueue;
循环数组queue用于存放队列中的元素。
3.6 函数QueueInit(size)
函数QueueInit(size)为队列分配一个容量为size的循环数组queue,并将队首游标front和队尾游标rear均置为0,创建一个空队列。
Queue QueueInit(int size)
{
Queue Q=(Queue)malloc(sizeof *Q);
Q->queue=(QItem *)malloc(size*sizeof(QItem));
Q->maxsize=size;
Q->front=Q->rear=0;
return 0;
}
3.7 函数QueueEmpty(Q)
检测队首游标front与队尾游标rear是否重合来判断队列Q是否为空队列。
int QueueEmpty(Queue Q)
{
return Q->front=Q->rear;
}
3.8 函数QueueFull(Q)
函数QueueFull(Q)检测队列Q的队尾插入一个元素后队首游标front与队尾游标rear是否重合来判断队列Q是否为满队列。
int QueueFull(Queue Q)
{
return(((Q->rear+1)%Q->maxsize==Q->front)?1:0);
}
3.9 函数QueueFirst(Q)
函数QueueFirst(Q)返回队列Q的队首元素。由于队首front指向队首元素的前一位置,所以队首元素在循环数组queue中的下标是(front+1)%maxsize。
QItem QueueFirst(Queue Q)
{//前提:队列非空
if(QueueEmpty(Q)) exit(1);
return Q->queue[(Q->front+1)%Q->maxsize];
}
3.10 函数QueueLast(Q)
返回存储队列Q的queue[rear]中队尾元素。
QItem QueueLast(Queue Q)
{//前提:队列非空
if(QueueEmpty(Q)) exit(1);
return Q->queue[Q->rear];
}
3.11 函数EnterQueue(x,Q)
先计算队尾元素的下一位置(rear+1)%maxsize,然后在该位置插入新元素x。
void EnterQueue(QItem x,Queue Q)
{
if(QueueFull(Q)) exit(1);
Q->rear=(Q->rear+1)%Q->maxsize;
Q->queue[Q->rear]=x;
}
3.12 函数DeleteQueue(Q)
思路:先找到队首元素,然后删除。
先将游标front修改为下一位置(front+1)%maxsize,然后返回该位置的元素,即队首元素。
QItem DeleteQueue(Queue Q)
{//前提:队列非空
if(QueueEmpty(Q)) exit(1);
Q->front=(Q->front+1)%Q->maxsize;
return Q->queue[Q->front];
}
以上用循环数组实现的队列基本运算都只要O(1)计算时间。
小结
(1)队列的基本概念是另一种特殊的表,这种表只在表首(队首)进行删除操作,只在表尾(为队尾)进行插入操作。由于队列的修改是按先进先出的规则进行的。
(2)用指针实现队列的方法和用循环数组实现队列的方法。