队列
- 队列的概念及结构
- 队列的实现
队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 。
入队列:进行插入操作的一端称为队尾。
出队列:进行删除操作的一端称为队头。
注:对于栈而言一种入栈顺序对应多种中出栈顺序;而对于队列而言一种入队顺序对应一种中出队顺序
队列的典型应用:
- 队列实际中要保证公平排队的地方都可以用队列
- 广度优先遍历
队列的实现
队列也可以数组和链表的结构实现,使用链表(单链表)的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
队列的定义
- 使用链表实现队列,一般写链式结构都要定义节点,因为链表它的结构上是一个一个的节点通过指针连接起来的。
- 使用单链表实现队列时,都会定义头指针以便找到链表中的所有节点,但是实现队列的出队列和入队列操作时就会导致头指针的改变,这时可以用二级指针传参 (传指针,解引用改变)、一级指针做返回值(接收返回值改变)(应用场景不是频繁调用的)、用带有哨兵位的头节点(不改变)这些方式保存“新的”头指针。此时要实现队列的入队列操作时使用单链表进行尾插时间效率太低了O(N),这时就会用尾指针来标记尾部节点便于尾插(尾插一个节点就更新尾指针即可)时间效率为O(1),这时实现队列就会用到两个指针一个是头指针一个是尾指针(凡是有两个指针的这种设计就会在定义一个结构体把它们包到一起),这时就会用到第四种方式利用结构体包一起 (结构体指针)来避免当前的头指针指向的不是头节点。
- 单链表的实现之所以不用结构体包一起方式设计,单链表这样设计是没有价值的,单链表设计时只留一个头指针指向它,而尾插时得去找尾,单链表就算有一个尾指针它能解决尾插的问题但是不能解决尾删的问题,干脆不给尾指针了反正尾插尾删效率低。
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QueueNode;
typedef struct Queue
{
QueueNode* phead;
QueueNode* ptail;
}Queue;
队列的初始化
队列的初始化只需将结构体内的两个指针置成空即可。
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
}
队列的销毁
队列的销毁需要将队列中的所有数据都删除。运用循环的方式将所有节点进行释放并将队列中的头尾指针置空即可。
void QueueDestory(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->phead;
while (cur)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
pq->phead = pq->ptail = NULL;
}
队列的入队列
入队列操作首先创建新节点,其次要判断队列中的两个成员是否为NULL,如果为NULL需要将新节点的地址直接赋值过去即可。否则将新节点直接入队列,更新队列中的尾指针即可。
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
if (pq->ptail == NULL)
{
pq->phead = pq->ptail = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
}
队列的出队列
出队列操作首先保存头节点的下一个节点,然后删除头节点,最后将头指针指向头节点的下一个节点即可。但是需要注意如果头节点的next指向的是NULL,那么释放头节点然后将头指针和尾指针置空即可(这样做可以避免尾指针出现野指针问题)。
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
if (pq->phead->next == NULL)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
else
{
QueueNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
}
获取队列中的数据个数
队列中的数据个数通过循环遍历的方式获取。但是注意如果这个接口函数偶尔调用可以用这样的方式实现,如果经常调用这个函数就需要在队列结构体中在添加一个成员size(用来记录数据个数,入队列和出队列的函数都得对size进行操作),这样效率更高。
int QueueSize(Queue* pq)
{
assert(pq);
// 如果频繁调用这个接口函数(给别人用的,跟别人对接的),可以在Queue中给一个size记录数据个数
int n = 0;
QueueNode* cur = pq->phead;
while (cur)
{
n++;
cur = cur->next;
}
return n;
}
队列的队头
队头数据通过头节点来获取数据。
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;
}
队列的判空
队列的判空只需判断队列中尾指针和头指针是否为空即可。
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->ptail == NULL && pq->phead == NULL;
}