数据结构:队列的顺序存储结构(循环队列)
队列的定义:
队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列的抽象数据类型:
ADT 队列(Queue) Data 同线性表,元素具有相同的类型,相邻元素具有前驱和后继关系。 Operation InitQueue(*Q): 初始化操作,建立一个空队列。 DestroyQueue(*Q): 若队列Q存在,则销毁它。 ClearQueue(*Q): 将队列清空。 QueueEmpty(Q): 判断队列是否为空。 GetHead(Q,*e): 若队列存在且非空,用e返回Q的队头元素。 EnQueue(*Q,e): 若队列Q存在,插入新元素e到队列Q中并成为队尾元素。 DeQueue(*Q,*e): 删除队列中的队头元素,并用e返回。 QueueLength(Q): 返回队列Q的元素的个数。 endADT
线性表有顺序存储和链式存储,栈是线性表,所以有这两种存储方式,有顺序栈和链栈。同样队列作为一种特殊的线性表,也存在这两种存储方式。先来看队列的顺序存储结构。
队列的顺序存储结构同线性表的顺序存储结构存在着同样的问题,队头出队列要后面的元素依次向前移动,时间复杂度为O(n)。
因为队列的每次删除元素都是从队头,所以每次删除都有大量的元素要移动,这样算法的效率很不好。于是改进一下,队头不一定要在数组的下标为0的位置。也就是说删除一个元素,仅需要把队头指针向后移动一次。
同时让front指针指向队头元素,rear指针指向队尾元素的下一个位置。这样当front等于rear时,队列就为空。
但是此时还有问题,会有假溢出的现象。
解决这种问题的办法就是循环队列。让上面的队尾指针rear指向数组的下标0位置。
队列的这种头尾相接的顺序存储结构称为循环队列。
但是还存在一个问题:当队列空或者满的时候都是front==rear。那么这个要怎么判断呢?
(1)设置一个标志变量flag,当fron==rear且flag==0时,队列为空;当front==rear且flag==1时队列满。
(2)当队列为空时,条件就是front==rear,当队列满时修改条件,保留一个元素空间。也就是说队列满时,数组还有一个空闲单元。
这里假设队列的最大尺寸为QueueSize,所以队列满的条件是(rear+1)%QueueSize == front。
队列的长度为(rear-front+QueueSize)%QueueSize。
循环队列代码实现:
#include <iostream> #include <stdlib.h> using namespace std; #define QueueSize 20 /******************************** * * 循环队列的顺序存储结构代码 * ********************************/ typedef int QElemType; typedef struct { QElemType data[QueueSize]; int front; int rear; }SqQueue; /******************************** * * 循环队列的操作函数实现代码 * ********************************/ /*初始化一个队列*/ bool InitQueue(SqQueue *Q) { Q->front = 0; Q->rear = 0; return true; } /*返回Q的元素的个数,也就是队列的当前长度*/ int QueueLength(SqQueue Q) { return (Q.rear-Q.front + QueueSize) % QueueSize; } /*若队列未满,插入元素e为Q的新队尾元素*/ bool EnQueue(SqQueue *Q, QElemType e) { if((Q->rear + 1) % QueueSize == Q->front) { return false; } cout << "EnQueue Item " << e << endl; Q->data[Q->rear] = e; Q->rear = (Q->rear + 1) % QueueSize; return true; } /*若队列不空,删除Q中队头的元素,用e返回其值*/ bool DeQueue(SqQueue *Q, QElemType *e) { if(Q->front == Q->rear) { return false; } *e = Q->data[Q->front]; Q->front = (Q->front + 1) % QueueSize; cout << "DeQueue Item " << *e << endl; return true; } /*将队列清空*/ bool ClearQueue(SqQueue *Q) { Q->front = 0; Q->rear = 0; return true; } /*判断队列是否为空*/ bool IsEmptyQueue(SqQueue Q) { return Q.front == Q.rear; } /*返回队头元素*/ bool GetTop(SqQueue Q, QElemType *e) { if(IsEmptyQueue(Q)) { return true; } *e = Q.data[Q.front]; cout << "Get Top Item:" << *e << endl; return true; } /*遍历队列中的各个元素*/ bool QueueTraverse(SqQueue Q) { if(IsEmptyQueue(Q)) { return false; } cout << "Queue Traverse ..." << endl; int count = (Q.rear - Q.front + QueueSize) % QueueSize; for(int i = Q.front; i < Q.front + count; i++) { cout << Q.data[i] << ' '; } cout << endl; return true; } void main(void) { SqQueue sq; InitQueue(&sq); for(int i = 0; i < 10; i++) { EnQueue(&sq, i); } QueueTraverse(sq); if(!IsEmptyQueue(sq)) { cout << "Queue Length: " << QueueLength(sq) << endl; } int result; GetTop(sq, &result); DeQueue(&sq, &result); DeQueue(&sq, &result); QueueTraverse(sq); for(int i = 0; i < 11; i++) { EnQueue(&sq, i); } GetTop(sq, &result); QueueTraverse(sq); system("pause"); }
执行结果:
单是顺序存储,若不是循环队列,算法的时间性能是不高的,但循环队列又面临着数组可能溢出的问题,所以还是要研究一下不需要担心队列长度的链式存储结构。