队列
1 代码位置
2 概念与结构
1.1概念
只允许在⼀端进行插⼊数据操作,在另⼀端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
⼊队列:进⾏插⼊操作的⼀端称为队尾
出队列:进⾏删除操作的⼀端称为队头
1.2结构
队列也可以数组和链表的结构实现,使⽤链表的结构实现更优⼀些,因为如果使⽤数组的结构,出队列在数组头上出数据,效率会⽐较低。
**数组头删时间复杂度:O(N) ** 数组尾插时间复杂度:O(1)
单链表头删时间复杂度:O(1) 单链表尾插时间复杂度:O(N)
- 乍一看二者难分伯仲,但如果我们在单链表的结构里再定义一个指向尾结点的指针,那么单链表就可以实现O(1)的尾插时间复杂度,而数组没有什么特别好的办法实现这种转变。
2 队列的实现
Queue.h
#pragma once #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <stdbool.h> typedef int QDataType; typedef struct QueueNode//队列节点的结构,即单链表节点的结构 { QDataType data; struct QueueNode* next; }QNode; typedef struct Queue//队列的结构,定义指向队列头尾的指针,以及队列节点的个数 { QNode* phead; QNode* ptail; QDataType size; }Q; void QueueInit(Q*); //入队列,队尾 void QueuePush(Q*, QDataType); //出队列,队头 void QueuePop(Q*); //队列判空 bool QueueEmpty(Q*); //取队头数据 QDataType QueueFront(Q*); //取队尾数据 QDataType QueueBack(Q*); //队列有效元素个数 int QueueSize(Q*); void QueueDestroy(Q*);
test.c
- 用来测试我们写的函数(函数的调用)
- 这一部分就是自己写的时候用的测试用例,随便什么都行
最好是写一个方法测试一次,不然找错误的时候会很痛苦😜
#define _CRT_SECURE_NO_WARNINGS 1 #include "Queue.h" void QueueTest01() { Q q;//定义队列 QueueInit(&q); QueuePush(&q, 1); QueuePush(&q, 2); QueuePush(&q, 3); QueuePush(&q, 4); /// printf("head:%d\n", QueueFront(&q)); printf("tail:%d\n", QueueBack(&q)); printf("size:%d\n", QueueSize(&q)); QueuePop(&q); QueueDestroy(&q); } int main() { QueueTest01(); return 0; }
Queue.c
函数方法的实现,重点重点!!!
在每一个方法的第一排都使用assert宏来判断形参是否为空(避免使用时传入空指针,后续解引用都会报错)
2.1 队列的初始化和销毁
2.1.1 初始化
前面提到,我们在定义队列时,一个结构体用来定义队列,其中有指向队列头尾的指针(其中还有一个size,用来保存链表长度,后面会讲到为什么),另一个就是队列结点的结构,和单链表一样
#define _CRT_SECURE_NO_WARNINGS 1 #include "Queue.h" void QueueInit(Q* pq) { assert(pq); pq->phead = pq->ptail = NULL; pq->size = 0; }
- 和单链表一样,初始化只需让指向队列的指针置为空即可,只不过这里多了一个尾指针,同时队列为空,size=0
- 链表空间都是按需申请,所以入数据都是通过之后的插入方法一个一个实现的
2.1.2 销毁
void QueueDestroy(Q* pq) { assert(pq); assert(!QueueEmpty(pq)); QNode* pcur = pq->phead; while (pcur) { QNode* next = pcur->next; free(pcur); pcur = next; } pq->phead = pq->ptail = NULL; pq->size = 0; }
同单链表
2.2 队列插入和删除数据
2.2.1 队尾插入数据(入队列)
同样的我们先写一个申请结点空间的函数
QNode* BuyNode(QDataType x) { QNode* newnode = (QNode*)malloc(sizeof(QNode)); if (newnode == NULL) { perror("malloc fail!"); exit(1); } newnode->data = x; newnode->next = NULL; return newnode; }
- 根据链表是否为空分两种情况
- 别忘了size++
void QueuePush(Q* pq, QDataType x) { assert(pq); if (pq->phead == NULL) pq->phead = pq->ptail = BuyNode(x); else { pq->ptail->next = BuyNode(x); pq->ptail = pq->ptail->next; } pq->size++; }
2.2.2 队头删除数据(出队列)
删除数据的时候一定要判断队列是否为空!(也可以通过size来判断)
bool QueueEmpty(Q* pq) { assert(pq); return pq->phead == NULL && pq->ptail == NULL; }
- 根据队列结点个数是否大于一个分两种情况讨论
- 别忘了size–
void QueuePop(Q* pq) { assert(pq); assert(!QueueEmpty(pq)); //只有一个节点的情况,避免ptail变成野指针 if (pq->ptail == pq->phead) { free(pq->phead); pq->phead = pq->ptail = NULL; } else { QNode* next = pq->phead->next; free(pq->phead); pq->phead = next; } pq->size--; }
2.3 返回队头/队尾数据
QDataType QueueFront(Q* pq) { assert(pq); assert(!QueueEmpty(pq)); return pq->phead->data; } QDataType QueueBack(Q* pq) { assert(pq); assert(!QueueEmpty(pq)); return pq->ptail->data; }
2.4 返回队列的有效数据个数
- size作用
- 首先队列和栈一样,不能进行遍历和随机访问,必须将队头出数据才能访问下一个,这样遍历求个数是不规范的
- 其次时间复杂度O(N),程序效率低
所以我们在队列结构里多定义了一个size,很好地解决了这个问题
int QueueSize(Q* pq) { assert(pq); //不规范且时间复杂度O(n) //int size = 0; //QNode* pcur = pq->phead; //while (pcur) //{ // size++; // pcur = pcur->next; //} //return size; return pq->size; }
Queue.c(完整版)
#define _CRT_SECURE_NO_WARNINGS 1 #include "Queue.h" void QueueInit(Q* pq) { assert(pq); pq->phead = pq->ptail = NULL; pq->size = 0; } QNode* BuyNode(QDataType x) { QNode* newnode = (QNode*)malloc(sizeof(QNode)); if (newnode == NULL) { perror("malloc fail!"); exit(1); } newnode->data = x; newnode->next = NULL; return newnode; } void QueuePush(Q* pq, QDataType x) { assert(pq); if (pq->phead == NULL) pq->phead = pq->ptail = BuyNode(x); else { pq->ptail->next = BuyNode(x); pq->ptail = pq->ptail->next; } pq->size++; } bool QueueEmpty(Q* pq) { assert(pq); return pq->phead == NULL && pq->ptail == NULL; } void QueuePop(Q* pq) { assert(pq); assert(!QueueEmpty(pq)); //只有一个节点的情况,避免ptail变成野指针 if (pq->ptail == pq->phead) { free(pq->phead); pq->phead = pq->ptail = NULL; } else { QNode* next = pq->phead->next; free(pq->phead); pq->phead = next; } pq->size--; } QDataType QueueFront(Q* pq) { assert(pq); assert(!QueueEmpty(pq)); return pq->phead->data; } QDataType QueueBack(Q* pq) { assert(pq); assert(!QueueEmpty(pq)); return pq->ptail->data; } int QueueSize(Q* pq) { assert(pq); //不规范且时间复杂度O(n) //int size = 0; //QNode* pcur = pq->phead; //while (pcur) //{ // size++; // pcur = pcur->next; //} //return size; return pq->size; } void QueueDestroy(Q* pq) { assert(pq); assert(!QueueEmpty(pq)); QNode* pcur = pq->phead; while (pcur) { QNode* next = pcur->next; free(pcur); pcur = next; } pq->phead = pq->ptail = NULL; pq->size = 0; }
对于队列这一种结构的实现,因为和单链表差异也不大,更细节的内容就没有过多赘述,对以单链表的实现方法有什么疑问的话,推荐先去看这一篇哦->单链表的实现方法