3.4.1抽象数据类型队列的定义
和栈相反,队列是一种先进先出(FIFO)线性表。它只允许在表的一端进行插入,在另一端进行删除元素。这和我们的排队是类似的,先来排在前面,办完事先走。在队列中允许插入的一端称为队尾(rear),允许删除的一端称为对头(front)。
队列的操作和栈类似,也有8个,不同的是队列是一个在表尾一个在表头,栈在栈顶。
3.4.2链队列——队列的链式表示和实现
和线性表类似,队列也可以由两种存储表示。
用链表表示的队列称为链队列。一个链队列显然需要两个分别指示队头和队尾的指针(分别称为头指针和尾指针)才能唯一确定。为了操作方便,我们也给链队列添加一个头结点(可以保证第一个结点也像其它结点一样有前驱),并令头指针指向头结点。空链队列的头和尾指针都指向头结点。
//------------------单链队列——队列的链式存储结构------------------------------------------------------------------------
typedef
struct
QNode{
QElemType
data;
struct
QNode
*next;
}QNode,*QueuePtr;
typedef
struct{
QueuePtr
front;
QueuePtr
rear; //和循环队列的顺序存储结构对比,循环队列的顺序存储结构,是用int 来描述front;链式是用自定义的指针*QueuePtr
}LinkQueue;
//------------------基本操作的函数原型说明------------------------------------
类似线性表链式存储结构的操作,所以省略。参考前面链式线性表。
//-----------------基本操作的算法描述(部分)-------------------------------
Status
InitQueue(LinkQueue &Q)
{
//构造一个空队列Q
Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode));
//和后面循环队列,构造一个空队列相比下
if(!Q.front)exit(OVERFLOW);
Q.front->next = NULL;
return OK;
}
Status
DestoryQueue(LinkQueue &Q)
{
//销毁队列Q
while(Q.front)
{
Q.rear = Q.front->next;
free(Q.front);
Q.front = Q.rear;
}
return OK;
}
Status
EnQueue(LinkQueue &Q,QElemType e)
{
//插入新的元素e到队列Q
p = (QueuePtr)malloc(sizeof(QNode)); //分配一个结点?队列结点QNode?
if(!p)exit(OVERFLOW);
p->data = e; p->next = NULL;
Q.rear->next = p;
Q.rear = p;
return OK;
}
Status
DeQueue(LinkQueue &Q,QElemType &e)
{
//删除非空队列Q的头元素,并用e返回
if(Q.front = Q.rear) return ERROR;
p = Q.front->next; e = p->data;
Q.front->next = p->next;
if(Q.rear ==p) Q.rear == Q.front; //如果p即是对头元素也是队尾元素,那么置队列为空;因为如果只有p那么p->next是不存在为NULL。
free(p);
return OK;
}
在上述模块的算法描述中,注意删除队列头元素算法中的特殊情况。一般情况下,删除队列头元素,只需要修改头指针就OK了,但是队列只有一个元素时候,删除后尾指针也就丢了,因此需要对队尾指针重新赋值(指向头结点)。
3.4.3循环队列——队列的顺序表示和实现
和顺序栈类似,在队列的顺序存储结构中,除了一组地址连续的存储单元存储队列元素外,还需要外设两个指针front和rear分别指示队列的头元素和尾元素的位置。为了在C语言中描述方便,在此我们约定:初始化建空队列时,令front=rear=0,每当插入新的队列尾元素时,”尾指针加1“;每当删除头指针元素时,”头指针增1“。
假设为一个队列分配的最大空间为6,则当处于下图(图d)的状态时,不能再插入元素,因为会越界而导致程序代码被破坏。然而,又不如顺序栈那样新增分配空间,因为队列的实际可用空间并未占满。一个巧妙的办法是将顺序队列臆造为一个环状的空间,如下图所示,称之为循环队列。
如下图(3.14a)所示循环队列中,队列头元素是J3,队列尾元素是J5,之后J6、J7和J8相继插入,则队列空间被占满。如图(b)所示,此时Q.front = Q.rear,反之,若J3、J4和J5相继从图(a)队列中删除,此时队列为空,如图(c)所示。此时,Q.front = Q.rear,由此可见,只凭等式 Q.front = Q.rear无法判断队列是空还是满。
有两种方法处理:其一是另设一个标志位以区别队列是空还是满;其二是少用一个元素空间,约定为当队列头指针在队列尾指针的下一个位置(指环状的下一位置)上作为队列成满状态标志。
在上述分析中可见,C语言中不能用动态分配的一维数组来实现循环队列。若用户应用程序中设有循环队列,那么必须为它设定一个最大长度,如果无法估测队列的最大长度,宜采用链队列:
循环队列类型的模块说明如下:
//------------------循环队列-----------队列的顺序存储结构-----------
#define MAXQSIZE 100 //最大队列长度
typedef struct{
QElemType *base; //初始化动态分配的存储空间,相当于指定分配的数组的基地址;
int front; //因为是队列的顺序存储结构,所以头结点用int型来描述,上面的链式存储结构就是用的自定义的指针*QueuePtr来描述。
int rear;
}SqQueue;
//-----------------循环队列的基本操作的算法描述--------------------
Status InitQueue(SqQueue &Q)
{
//构造一个空队列Q
Q.base = (QElemType *)malloc(MAXQSIZE*sizeof(QElemType));
if(!Q.base)exit(OVERFLOW);
Q.front = Q.rear;
return OK;
}
int QueueLength(SqQueue Q)
{
//返回队列元素的个数,即队列的长度
return(Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;
}
Status EnQueue(SqQueue &Q, QElemType e)
{
//插入元素e到队列尾
if((Q.rear + 1) % MAXQSIZE == Q.front) return ERROR; //队列满
Q.base[Q.rear] = e;
Q.rear = (Q.rear + 1) % MAXQSIZE;
return OK;
}
Status DeQueue(SqQueue &Q, QElemType &e)
{
//删除队列头元素,用e返回
if(Q.front == Q.rear) return ERROR;
e = Q.base[Q.front];
Q.front = (Q.front + 1) % MAXQSIZE;
return OK;
}