程序猿必修课之数据结构(八)队列

来源:互联网 发布:codewarrior软件 编辑:程序博客网 时间:2024/05/18 03:52

文本同步发布于简书:http://www.jianshu.com/p/5d020c00fcb8

上一章:程序猿必修课之数据结构(七)栈2

队列的定义

队列(Queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。

队列的抽象数据类型

队列是特殊的线性表,因此它的各种操作类似线性表,不同的是插入数据只能在队尾进行,删除数据只能在队头进行。

ADT 队列(Queue)Data    同线性表。元素的类型相同,相邻元素具有前驱和后继关系。Operation    initQueue(*Q): 队列初始化,建立一个空队列。    destroyQueue(*Q): 若队列 Q 存在,就销毁它。    clearQueue(*Q): 将队列清空。    isEmpty(*Q): 若队列为空,返回 true;否则返回 false。    getHead(Q, *e): 若队列 Q 存在且不为空,则用 e 返回队列 Q 中的队头元素。    enQueue(*Q, e): 若队列 Que 存在,插入新元素 e 到队列 Q 中并成为队尾元素。    deQueue(*Q, *e): 删除队列 Q 中队头元素,并用 e 返回。    getLength(Q): 返回队列 Q 的元素个数。endADT 

循环队列

我们把头尾相接的队列的顺序存储结构称为循环队列。

用 front 指针指向队头元素,用 rear 指针指向队尾元素的下一个位置(为了避免数组越界,队尾元素后要留一个空闲单元,保存 rear 指针)。所以当 front == rear 时,表示队列为空;当数组中只有一个空闲单元时队列就满了。

因为是循环队列,所以 rear 可能比 front 大,也可能比 fornt 小,如下图所示:

可以看出,它们可能只相差一个位置,但也可能相差整整一圈。所以若队列的最大容量为 queueSize,那么队列满的条件是:(rear + 1) % queueSize == front。

当 rear > front 时(即左图),队列的长度为 rear - front;当 rear < front 时(即右图),队列的长度由两部分构成,一部分是 queueSize - front,另一部分是 0 + rear,所以队列长度为 rear - front + queueSize。所以通用的计算队列长度的公式为:

(rear - front + QueueSize) % queueSize

循环队列的顺序存储结构

define MAXSIZE 10typedef struct {  int data[MAXSIZE];  int front;  int rear;} Queue;

初始化:

void initQueue(Queue *q) {  q->front = 0;  q->rear = 0;}

求队列长度

int queueLength(Queue q) {  return (q->rear - q->front + MAXSIZE) % MAXSIZE;}

入队操作:

boolean enQueue(Queue *q, int e) {  /* 队列已满 */  if ((q->rear + 1) % MAXSIZE == q->front)    return false;  q->data[q->rear] = e;  q->rear = (q->rear + 1) %  MAXSIZE;  return true; }

出队操作:

boolean deQueue(Queue *q, int *e) {  /* 队列为空 */  if (q->front == q->rear)    return false;  *e = q->data[q->front];  q->front = (q->front + 1) % MAXSIZE;  return true;}

虽然循环队列解决了出队时算法的时间复杂度不高的问题,但是它仍然有数组可能会溢出的问题,所以还需要链式存储结构来解决这个问题。

队列的链式存储结构及实现

队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,简称为链队列。

为了方便操作,队头指针指向链队列的头结点;尾指针指向终端结点。当 front 和 rear 都指向头结点时,队列为空。

链队列的结构为:

typedef struct QNode {  int data;  struct QNode *next;}QNode, *pQueue;typedef struct{  pQueue front;  pQueue rear;}LinkQueue;

入队操作

boolean enQueue(LinkQueue *q, int e) {  pQueue s = (pQueue)malloc(sizeof(QNode));  if(!s)    return false;  s->data = e;  s->next = NULL;  q->rear->next = s;  q->rear = s;  return true;}

出队操作

出队操作就是头结点的后继结点 p 出队,将头结点的后继改为 p 的后继结点,如果p 是 rear 指向的结点,那么还要将 rear 指向头结点。

boolean deQueue(LinkQueue *q, int *e) {  pQueue p;  if (q->front == q->rear)    return false;  p = q->front->next;  *e = p->data;  q->front->next = p->next;  if(q->rear == p)    q->rear = q->front;  free(p);  return true;}

比较循环队列和链队列

从时间复杂度,它们的基本操作都是 O(1);但是循环队列需要先申请好空间,使用期间不释放,而对于链队列,每次申请和释放结点也会存在一些时间开销。

从空间上来说,循环队列必须有一个固定的长度,这样就可能存在空间浪费的问题;而链队列不存在这个问题,尽管它需要一个指针域。

总的来说,在可以确定队列长度最大值的情况下,建议用循环队列,反之用链队列。

0 0
原创粉丝点击