严蔚敏《数据结构》(C语言版)【教材精讲+考研真题解析】讲义与视频课程【36小时高清视频】
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.3 队 列

视频二维码(扫码观看)

一、抽象数据类型队列的定义

1队列的基本概念

队列(Queue):也是运算受限的线性表。是一种先进先出(First In First Out,简称FIFO)的线性表。只允许在表的一端进行插入,而在另一端进行删除。

队首(front):允许进行删除的一端称为队首。

队尾(rear):允许进行插入的一端称为队尾。

队列中没有元素时称为空队列。在空队列中依次加入元素a1,a2,…,an之后,a1是队首元素,an是队尾元素。显然退出队列的次序也只能是a1,a2,…,an,即队列的修改是依先进先出的原则进行的,如图3-5所示。

图3-5 队列示意图

2队列的抽象数据类型定义

二、链队列——队列的链式表示和实现

1队列的链式存储表示

队列的链式存储结构简称为链队列,它是限制仅在表头进行删除操作和表尾进行插入操作的单链表。

需要两类不同的结点:数据元素结点,队列的队首指针和队尾指针的结点,如图3-6所示。

图3-6 链队列结点示意图

数据元素结点类型定义:

指针结点类型定义:

2链队运算及指针变化

链队的操作实际上是单链表的操作,只不过是删除在表头进行,插入在表尾进行。插入、删除时分别修改不同的指针。链队运算及指针变化如图3-7所示。

图3-7(a) 空队列指针

图3-7(b) x入队后指针变化

图3-7(c) y再入队后指针变化

图3-7(d) x出队后指针变化

3链队列的基本操作

(1)基本操作的函数原型说明

Status Initqueue(LinkQueue &Q);//构造一个空队列Q。

Status DestroyQueue(LinkQueue &Q);//销毁队列Q,Q不再存在。

Status ClearQueue(LinkQueue &Q);//将Q清为空队列。

Status Queue Empty(LinkQueue Q);//若队列Q为空队列,则返回TRUE,否则返回FALSE。

int queueLength(LinkQueue Q);//返回Q的元素个数,即为队列的长度。

Status GetHead(LinkQueue Q,QElemlype &e);//若队列不空,则用e返回Q的队头元素,并返回OK;否则返回ERROR。

Status EnQueue(LinkQueue &0,QElem Type e);//插入元素e为Q的新的队尾元素。

Status DeQueue(LinkQueue &Q,QElem Type &e);//若队列不空,则删除Q的队头元素,用e返回其值,并返回OK;否则返回ERROR。

Status QueueTraverse(LinkQueue Q,visit());//从队头到队尾依次对队列Q中每个元素调用函数visit(),一且visit失败,则操作失败。

(2)部分基本操作的算法描述

链队列的初始化

链队列的入队操作

链队列的出队操作

链队列的撤消

三、循环队列——队列的顺序表示和实现

1队列的顺序存储表示

设立一个队首指针front,指向队首元素;一个队尾指针rear,指向队尾元素的下一位置。

初始化:front=rear=0。

入队:将新元素插入rear所指的位置,然后rear加1。

出队:删去front所指的元素,然后front加1并返回被删元素。

队列为空:front=rear。

队满:rear=MAX_QUEUE_SIZE-1或front=rear。

在非空队列里,队首指针始终指向队头元素,而队尾指针始终指向队尾元素的下一位置。

顺序队列中存在“假溢出”现象。因为在入队和出队操作中,头、尾指针只增加不减小,致使被删除元素的空间永远无法重新利用。因此,尽管队列中实际元素个数可能远远小于数组大小,但可能由于尾指针已超出向量空间的上界而不能做入队操作。该现象称为假溢出。如图3-8所示是数组大小为5的顺序队列中队首、队尾指针和队列中元素的变化情况。

图3-8 顺序队列中队首、队尾指针和队列中元素的变化情况

2循环队列

为充分利用向量空间,克服上述“假溢出”现象的方法是:将为队列分配的向量空间看成为一个首尾相接的圆环,并称这种队列为循环队列(Circular Queue)。

在循环队列中进行出队、入队操作时,队首、队尾指针仍要加1,朝前移动。只不过当队首、队尾指针指向向量上界(MAX_QUEUE_SIZE-1)时,其加1操作的结果是指向向量的下界0。

这种循环意义下的加1操作可以描述为:

if(i+1==MAX_QUEUE_SIZE)
    i=0;
else
    i++;

其中,i代表队首指针(front)或队尾指针(rear)

用模运算可简化为:i=(i+1)%MAX_QUEUE_SIZE;

显然,为循环队列所分配的空间可以被充分利用,除非向量空间真的被队列元素全部占用,否则不会上溢。因此,真正实用的顺序队列是循环队列。

【例】有循环队列QU[0,5],其初始状态是front=rear=0,各种操作后队列的头、尾指针的状态变化情况如下图3-9所示。

图3-9(a) 空队列指针

图3-9(b) d,e,b,g入队后指针变化情况

图3-9(c) d,e出队后指针变化情况

图3-9(d) i,j,k入队后指针变化情况

图3-9(e) b,g出队后指针变化情况

图3-9(f) r,p入队后指针变化情况

入队时尾指针向前追赶头指针,出队时头指针向前追赶尾指针,故队空和队满时头尾指针均相等。因此,无法通过front=rear来判断队列“空”还是“满”。解决此问题的方法是:约定入队前,测试尾指针在循环意义下加1后是否等于头指针,若相等则认为队满。即:

rear所指的单元始终为空。

循环队列为空:front=rear。

循环队列满:(rear+1)%MAX_QUEUE_SIZE=front。

3部分循环队列的基本操作:

循环队列的初始化

Status InitQueue(SqQueue &Q)
{//构造一个空队列Q
    Q.base=(QElemType*)malloc(MAXQSIZE *sizeof(QElemType));
    i(!Q.base)
        exit(OVERFLOW);
    Q.front=Q.rear=0
    return OK;
}

入队操作

Status EnQueue(SqQueue &Q,QElemType e)
{//插入元素e为Q的新的队尾元素
    if((Q.rear+1)% MAXQSIZE)==Q.front)
        return ERROR;
    Q.base[Q.rear]=e;
    Q.rear=(Q.rear +1)%MAXOSIZE;
    return OK
}

出队操作

Status DeQueue(SqQueue &Q,QelemType &e)
{//若队列不空,则删除Q的队头元素,用e返回其值,并返回OK;否则返回ERROR。
    if(Q.front==Q.rear)
        return ERROR;
    e=Q.base[Q.front];
    Q.front=(Q.front+1)%MAXQSIZE;
    return OK;
}