队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
链式队列:链表的头当做队头,链表的尾当做队尾,这样在尾部插入与在头部删除比较简单。
我们使用无头、单向、非循环链表来实现链式队列,因为队列需要频繁的队头和队尾操作,所以我们定义头指针和尾指针。
因为我们使用的是无头链表实现,所以插入时需要对空链表单独处理,删除时,需要对只有一个元素的情况单独处理。
结点定义:因为使用单链表实现队列,所以队列结点与单链表结点相同。
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
由于频繁对队头、队尾进行操作,所以有head和tail两个指针:
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
我们使用size变量记录队列中的元素个数,这样可以直接返回该值,但在插入和删除时要维护该size的值。如果不加size成员,就需要遍历链表,累计size:
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
可以通过头尾指针都为空来判断队列是否为空,也可以通过队列的元素size是否等于0来判断队列是否为空。
bool QueueEmpty(Queue* pq)
{
assert(pq);
/*return pq->phead == NULL
&& pq->ptail == NULL;*/
return pq->size == 0;
}
队尾入队,也就是对链表的尾插。
单链表进行尾插时需要找到尾结点,由于这里我们使用ptail尾指针就是指向尾结点,因此找尾这一步省去了。
找到尾结点后需要插入到尾结点后,值得说的是,由于插入时单链表可能为空,因此需要对为空的情况单独处理:
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
// 待插入结点
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail\n");
return;
}
newnode->data = x;
newnode->next = NULL;
if (pq->ptail == NULL)
{
// 1.链表为空
assert(pq->phead == NULL);
pq->phead = pq->ptail = newnode;
}
else
{
// 2.链表不为空直接插入尾结点后
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
队首出队,也就是链表的头删。
删除时需要判空处理,队列中至少有一个元素才能进行删除,我们可以使用断言实现。
对于没有尾指针的单链表头删非常简单,只需要将头指针指向头结点下一个结点。
但是我们使用单链表实现队列时,附加了尾指针,这时链表的头删还需要考虑尾指针,直白的说就是在删除最后一个元素时,还要修改尾指针的指向。
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
if (pq->phead->next == NULL)
{
// 删除最后一个元素时修改尾指针指向
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
else
{
// 正常头删
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
头指针phead指向头结点,便找到链表第一个元素,也就是队首元素。
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->phead->data;
}
尾指针指向尾结点,便找到链表最后一个元素,也就是队尾元素。
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->ptail->data;
}
Gitee-Queue
链式队列的实现本质上是对单链表进行操作。
入队和出队分别对应单链表的尾插和头删。因为是无头单向非循环的单链表,因此尾插时需要对空链表单独处理。又因为我们需要维护尾指针,因此头删时,删除最后一个元素时还需要对尾指针单独处理。这些在上面代码中都有所体现。