队列是一种十分常见的数据结构,它的应用范围非常广泛,例如在操作系统中调度算法、人工客服排队序列等。队列要求删除元素的操作必须在队列头部,这个过程称为出队(pop),而队列插入元素的操作必须发生在队列尾部,这个过程称为进队(push)。队列不允许随意访问其中元素,能够直接访问的元素只有队头元素。
顺序队列。顺序队列需要或动态或静态分配一块连续的内存,设置队头指针front指向队头元素和队尾指针rear指向队尾指针的下一个元素。如下图时代表内存地址为1,2,3时是队列元素,而4并不是。
这种结构的队列由于入队和出队操作中,头尾指针只增加不减小,致使出队元素的空间永远无法重新利用。虽然队列中实际的元素个数远远小于向量空间的规模时,rear指针也可能已超越向量空间的上界。这种情况下或者当队列已满时进行进队操作会导致溢出,进行进队操作之前应当检测队尾指针是否溢出。这样的队列判空条件 front == rear。
链式队列。因为队列内部元素不允许随意访问,只能在对头或队尾操作,所以如果使用链表实现队列,那么链表元素访问不便的缺点也无关紧要了,更重要的是,使用链表实现队列的话,理论上队列大小只取决于内存大小,并且出队元素的空间可以被释放重新利用,进队元素即时申请内存。相较于顺序结构队列,容量以及空间利用率都大幅提高。链式队列设置的队头指针front指向队头节点,队尾指针rear指向队尾节点。判空条件front或rear为空(与顺序结构队列不同,当front==rear时队列有一个元素)。
循环队列。循环队列是在顺序结构队列的基础之上优化,充分利用存储空间,并且减少了维护队列结构的开销。循环队列在结构上与顺序队列没有太大区别。不过当队头指针front或队尾指针rear到达存储边界时,下一次移动并非继续向前移动,而是移动至存储空间的第一个位置。如下图,若现在有一个元素进队之后,rear将移动至1处。
在逻辑上可以把这块区域看作是一块环状空间,使得队列可以循环使用,队列满时会覆盖队头元素,front向前移动一个单位。但是这样会导致一个问题,当队列为空时有front==rear,当队列满时,也有front==rear。为了避免这种情况的发生,我们通常的做法是在最大存储空间(maxSize)的基础之上添加一个空的存储单元,比如上图有九个存储空间但是真正作为队列使用的maxSize只有八个,rear指向的单元永远不会有队列元素。这样当front==rear时队列为空,当front==(rear + 1)%maxSize时队列已满,实时队列大小为(rear - front + maxSize) % maxSize。
基于链表的链式队列
设计链式节点的结构
typedef struct Node {
dataType data; //数据域
struct Node *next; //指针域
} *list;
设计队列结构
typedef struct {
list front; //队头节点
list rear; //队尾节点
int size; //队列大小
} queue;
相关操作
queue createQueue() { //创建一个新的队列
queue res;
res.front = res.rear = NULL;
res.size = 0;
return res;
}
bool isEmpty(queue Q) { //判断一个队列是否为空
return Q.size == 0;
}
dataType getFront(queue Q) { //获取队头元素
return Q.front->data;
}
void pop(queue &Q) { //队头元素出队
list p = Q.front;
Q.front = Q.front->next;
Q.size--;
free(p);
}
void push(queue &Q, dataType value) { //元素进入队尾
list p = (list)malloc(sizeof(list));
p->next = NULL;
p->data = value;
if(isEmpty(Q)) { //若队列为空,前后队头队尾均为新添加元素
Q.front = Q.rear = p;
}
else { //队列不为空,移动队尾
Q.rear->next = p;
Q.rear = p;
}
Q.size++;
}
int getSize(queue Q) { //获取队列大小
return Q.size;
}
动态分配的循环链表
#define dataType int //队列数据类型
typedef struct {
dataType *start;
int front; //队头指针
int rear; //队尾指针
int maxSize; //队列最大存储量
} circularQueue;
circularQueue createCircularQueue(int maxSize) { //创建一个新的循环队列,参数为队列最大存储数量
circularQueue res;
res.start = (dataType *)malloc(sizeof(dataType) * (maxSize + 1));
res.maxSize = maxSize;
res.front = res.rear = 0;
return res;
}
bool isEmpty(circularQueue Q) { //判断一个循环队列是否为空
return Q.front == Q.rear;
}
bool isFull(circularQueue Q) { //判断队列是否已满
return ((Q.rear + 1) % (Q.maxSize + 1)) == Q.front;
}
dataType getFront(circularQueue Q) { //获取队头元素
return Q.start[Q.front];
}
void pop(circularQueue &Q) { //队头元素出队
Q.front = (Q.front + 1) % (Q.maxSize + 1);
}
void push(circularQueue &Q, dataType value) { //元素进入队尾
if(isFull(Q)) { //如果队列已满,队头元素将被覆盖,队头指针需要移动
pop(Q);
}
Q.start[Q.rear] = value;
Q.rear = (Q.rear + 1) % (Q.maxSize + 1);
}
int getSize(circularQueue Q) { //获取队列大小
return (Q.rear - Q.front + Q.maxSize + 1) % (Q.maxSize + 1);
}
文件下载