队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)规则。
入队列:进行插入操作的一端称为队尾。
如果使用数组实现,要同时实现首尾的操作,头部的操作无论是删除还是插入,效率都较低。
使用链表实现时,头部操作很方便,对于尾部操作,我们可以加一个尾指针rear,这样可以很方便的实现尾插和头删,因此可以将链表头作为队头,链表尾作为队尾。
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
typedef struct Queue
{
QNode* front;
QNode* rear;
int size;
}Queue;
用单链表作为队列的结点,队列中包含头尾指针便于头删尾插,和取出首尾元素,size便于返回队列中的元素总数。
//队列的初始化
void QueueInit(Queue* pq);
//队列的销毁
void QueueDestroy(Queue* pq);
//队尾入队列
void QueuePush(Queue* pq, QDataType x);
//队头出队列
void QueuePop(Queue* pq);
//检查队列是否为空
bool QueueEmpty(Queue* pq);
//检验队列中有效元素的个数
int QueueSize(Queue* pq);
//返回队头元素
QDataType QueueFront(Queue* pq);
//返回队尾元素
QDataType QueueBack(Queue* pq);
//队列的初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->front = pq->rear = NULL;
pq->size = 0;
}
分别对队列中的首尾指针、size进行初始化即可,因为有多个元素,因此最好封装成函数,若是单链表,只用一个指针指向,就不用将初始化封装了。
//队列结点的生成
QNode* BuyQnode(QDataType x)
{
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (NULL == newnode)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* tmp = BuyQnode(x);
if (NULL == tmp)
{
perror("BuyQnode fail");
return;
}
QNode* newnode = tmp;
if (NULL == pq->front)
{
pq->front = pq->rear = newnode;
}
else
{
pq->rear->next = newnode;
pq->rear = newnode;
}
pq->size++;
}
先用BuyQnode得到新结点。
如果是第一次入队列,首尾指针front和rear都要改变。不是第一次的话,链接到rear后,然后更新rear的指向即可。最后不要忘了将size++
//检查队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->front == NULL;
}
先进行判空操作。可以用front是否指向空判断,也可以用size是否等于0判断。
然后进行出队,即头删。
//队头出队列
void QueuePop(Queue* pq)
{
assert(pq);
//先判断队列不为空
assert(!QueueEmpty(pq));
if (pq->front == pq->rear)
{
//指向唯一一个结点
free(pq->front);
pq->front = pq->rear = NULL;
}
else
{
QNode* del = pq->front;
pq->front = pq->front->next;
free(del);;
}
pq->size--;
}
有多个元素时,先用del保存要删除的位置,然后让front指向下一个元素,作为新的头,然后free掉del位置的元素,完成删除。
如果只有一个元素,删除后front指向空,但是rear尾指针仍然指向原来唯一的元素,此时为空指针,因此应该直接free掉这个,然后将front和rear都置为空。
删除完成后不要忘了size--
和栈相似,由于先进先出或者先进后出的顺序结构,栈和队列没法随机访问,得到任意位置的元素。需要利用循环,队列不为空时,取出队头元素,然后出队头Pop的元素,然后才能得到后面的数据。
首先要保证队列不为空,为空的话不能取出元素。
然后返回首尾指针指向结点中保存的数据即可。
直接返回size代表的元素个数即可。
用cur从头开始遍历整个队列,保存next,将cur置空后,再指向下一个即可。
实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型
时可以就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。
假设队列为空时,front和rear指向同一个位置,每次在rear位置尾插,然后rear+1,若总长度为k,插入k位置的元素后,rear又指向了front的位置,因此我们无法区分空队列和满队列。
因此在开始时,我们便多开一个位置,使得队列满时,有一个空的位置让rear指向,从而区分空/满
删除时让front指向下一个位置即可,front指向的是队头,rear指向的是队尾的后一个位置,因此取尾的数据是rear-1的位置。
有效数据的范围是front--rear-1,只要不为空,即front和rear指向位置不同时就可以删除。
同时入队的时候要保证队列不满。
在用数组模拟实现时,rear指向k位置时,arr[rear]=x;此时rear+1会越界,可以采用(rear+1)%(k+1),让rear回到0位置。判断是否队满时也要这样操作。
同时取尾元素时,要得到arr[rear-1],如果此时rear为0,就会导致越界,要让它经过处理变为k位置。而其它情况下保持不变仍为rear-1,可用(rear+k)% (k+1)n=0时,得到k,n=k时得到k-1