【数据结构】队列详解
一、队列(Queue)
01 队列的概念
概念:
① 队列只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。
② 入队列,进行插入操作的一端称为 队尾。出队列,进行删除操作的一端称为 队头。
③ 队列中的元素遵循先进先出的原则,即 FIFO 原则(First In First Out)。
02 队列的结构
结构:
二、队列的定义
01 链式队列
typedef int QueueDataType;typedef struct QueueNode {struct QueueNode* next;QueueDataType data;
} QueueNode;typedef struct Queue {QueueNode* pHead;QueueNode* pTail;
} Queue;
02 接口函数
这是需要实现几个接口函数:
// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QueueDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QueueDataType QueueFront(Queue* q);
// 获取队列队尾元素
QueueDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空
bool QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
三、队列的实现
01 初始化(QueueInit)
Queue.h:
// 初始化队列
void QueueInit(Queue* q);
Queue.c:
/* 初始化队列 */
void QueueInit(Queue* q) {assert(q);q->pHead = q->pTail = NULL;
}
解析:首先使用断言防止传入的 q 为空。初始化只需要把头指针和尾指针都置成空即可。
02 销毁(QueueDestory)
Queue.h:
// 销毁队列
void QueueDestroy(Queue* q);
Queue.c:
/* 销毁队列 */
void QueueDestroy(Queue* q) {assert(q);QueueNode* cur = q->pHead;while (cur) {QueueNode* curNext = cur->next;free(cur);cur = curNext;}q->pHead = q->pTail = NULL;
}
解读:
① 首先断言防止传入的 q 为空。
② 销毁要把所有节点都释放掉,我们创建遍历指针 cur 遍历整个队列。既然要释放 cur 指向的节点,为了防止释放 cur 之后找不到其下一个节点导致无法移动,我们这里创建一个类似于信标性质的指针 curNext 来记录一下 cur 的下一个节点,之后再 free 掉 cur,这样就可以移动 cur 了。
③ 最后为了防止野指针,还需要把头指针和尾指针都置为空。
03 判断队列是否为空(QueueEmpty)
Queue.h:
// 检测队列是否为空
bool QueueEmpty(Queue* q);
Queue.c:
/* 检测队列是否为空 */
bool QueueEmpty(Queue* q) {assert(q);return q->pHead == NULL;
}
04 入队(QueuePush)
Queue.h:
// 队尾入队列
void QueuePush(Queue* q, QueueDataType data);
Queue.c:
/* 队尾入队列 */
void QueuePush(Queue* q, QueueDataType data) {assert(q);// 创建新结点QueueNode* new_node = (QueueNode*)malloc(sizeof(QueueNode));if (new_node == NULL) {printf("malloc failed");exit(-1);}new_node->data = data;new_node->next = NULL;/* 情况1:队列为空:既当头又当尾* [new_node]* ↑ ↑* pHead pTail*情况2:队列不为空:队尾入数据* [] ->[] ->[] ->[] ->[new_node]* pHead pTail pTail->next*/if (q->pHead == NULL) {q->pHead = q->pTail = new_node;} else {q->pTail->next = new_node;q->pTail = new_node;}
}
解读:
① 首先断言防止传入的pQ为空。
② 我们首先要创建新节点。通过 malloc 动态内存开辟一块 QueueNode 大小的空间。最后放置数据,将待插入的数据 data 交给 data,next 默认置空,和之前学链表一样,这里就不过多赘述了。
③ 新节点创建好后,我们可以开始写入队的操作了。首先要理解队列的性质:队尾入数据,队头出数据。这里既然是入队,就要在队尾进行插入。这里我们还要考虑到如果队列为空的情况,这时我们要把头指针和尾指针都交付给 new_node 。
当队列为空时,令头指针和尾指针都指向 new_node ,当队列不为空时,在尾部的下一个节点放置 new_node ,随后再更新尾指针让其指向新的队尾。
05 出队(QueuePop)
Queue.h:
// 队头出队列
void QueuePop(Queue* q);
Queue.c:
/* 队头出队列 */
void QueuePop(Queue* q) {assert(q && !QueueEmpty(q));QueueNode* headNext = q->pHead->next;free(q->pHead);q->pHead = headNext;if (q->pHead == NULL)q->pTail = NULL;
}
解读:
① 首先断言防止传入的 q 为空,这里还要防止队列为空,如果队列为空还要求出队的话会出问题,所以这里要断言一下 QueueEmpty 为假。
② 思路草图如下:
出数据需要释放,和销毁一样,这里使用一个类似于信标性质的指针来记录 pHead 的下一个节点,之后我们就可以大胆地释放 pHead 了。free 掉之后更新头即可,令头指针指向 headNext 即可。
注意:这里还要考虑一个问题,如果队内都被删完了,pHead 往后走指向空,但是 pTail 仍然指向那块已经被 free 掉的空间。pTail 就是一个典型的野指针。我们可以不用担心 pHead,因为后面没有数据他会自然指向 NULL,但是我们这里得关注 pTail ,手动处理一下它。如果 pHead 为空,我们就把 pTail 也置为空即可。
06 返回队头数据(QueueFront)
Queue.h:
// 获取队列头部元素
QueueDataType QueueFront(Queue* q);
Queue.c:
/* 获取队列头部元素 */
QueueDataType QueueFront(Queue* q) {assert(q && !QueueEmpty(q));return q->pHead->data;
}
07 返回队尾数据(QueueBack)
Queue.h:
// 获取队列队尾元素
QueueDataType QueueBack(Queue* q);
Queue.c:
/* 获取队列队尾元素 */
QueueDataType QueueBack(Queue* q) {assert(q && !QueueEmpty(q));return q->pTail->data;
}
08 获取队列中有效元素个数(QueueSize)
Queue.h:
// 获取队列中有效元素个数
int QueueSize(Queue* q);
Queue.c:
/* 获取队列中有效元素个数 */
int QueueSize(Queue* q) {assert(q);int cnt = 0;QueueNode* cur = q->pHead;while (cur) {++cnt;cur = cur->next;}return cnt;
}
解读:这里我们采用计数器法来求大小即可,调用一次就是 O(N) ,也没什么不好的。
① 首先断言防止传入的 q 为空。
② 创建计数器变量和遍历指针 cur,遍历整个队列并计数,最后返回计数的结果即可。
09 完整代码
Queue.h:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>typedef int QueueDataType;typedef struct QueueNode {struct QueueNode* next;QueueDataType data;
} QueueNode;typedef struct Queue {QueueNode* pHead;QueueNode* pTail;
} Queue;// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QueueDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QueueDataType QueueFront(Queue* q);
// 获取队列队尾元素
QueueDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空
bool QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
Queue.c:
#include "Queue.h"/* 初始化队列 */
void QueueInit(Queue* q) {assert(q);q->pHead = q->pTail = NULL;
}/* 队尾入队列 */
void QueuePush(Queue* q, QueueDataType data) {assert(q);// 创建新结点QueueNode* new_node = (QueueNode*)malloc(sizeof(QueueNode));if (new_node == NULL) {printf("malloc failed");exit(-1);}new_node->data = data;new_node->next = NULL;/* 情况1:队列为空:既当头又当尾* [new_node]* ↑ ↑* pHead pTail*情况2:队列不为空:队尾入数据* [] ->[] ->[] ->[] ->[new_node]* pHead pTail pTail->next*/if (q->pHead == NULL) {q->pHead = q->pTail = new_node;} else {q->pTail->next = new_node;q->pTail = new_node;}
}/* 队头出队列 */
void QueuePop(Queue* q) {assert(q && !QueueEmpty(q));QueueNode* headNext = q->pHead->next;free(q->pHead);q->pHead = headNext;if (q->pHead == NULL)q->pTail = NULL;
}/* 获取队列头部元素 */
QueueDataType QueueFront(Queue* q) {assert(q && !QueueEmpty(q));return q->pHead->data;
}/* 获取队列队尾元素 */
QueueDataType QueueBack(Queue* q) {assert(q && !QueueEmpty(q));return q->pTail->data;
}/* 获取队列中有效元素个数 */
int QueueSize(Queue* q) {assert(q);int cnt = 0;QueueNode* cur = q->pHead;while (cur) {++cnt;cur = cur->next;}return cnt;
}/* 检测队列是否为空 */
bool QueueEmpty(Queue* q) {assert(q);return q->pHead == NULL;
}/* 销毁队列 */
void QueueDestroy(Queue* q) {assert(q);QueueNode* cur = q->pHead;while (cur) {QueueNode* curNext = cur->next;free(cur);cur = curNext;}q->pHead = q->pTail = NULL;
}