1、队列
1.1 队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出
FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头
2、队列的实现
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数
组头上出数据,效率会比较低。本篇文章就是用链表实现队列。
2.1 接口
#include <stdio.h> #include <stdlib.h> #include <assert.h> // 链式结构:表示队列 typedef int QDataType; typedef struct QListNode { struct QListNode* next; QDataType data; }QNode; // 队列的结构 typedef struct Queue { QNode* front; QNode* rear; int size; }Queue; // 初始化队列 void QueueInit(Queue* q); // 队尾入队列 void QueuePush(Queue* q, QDataType data); // 队头出队列 void QueuePop(Queue* q); // 获取队列头部元素 QDataType QueueFront(Queue* q); // 获取队列队尾元素 QDataType QueueBack(Queue* q); // 获取队列中有效元素个数 int QueueSize(Queue* q); // 检测队列是否为空,如果为空返回非零结果,如果非空返回0 int QueueEmpty(Queue* q); // 销毁队列 void QueueDestroy(Queue* q);
3、接口的实现
3.1 初始化队列
我们将队头指针与队尾指针都置为 NULL,并将队列的大小赋值为 0。
void QueueInit(Queue* q) { assert(q); q->front = NULL; q->rear = NULL; q->size = 0; }
3.2 队尾入队列
void QueuePush(Queue* q, QDataType data) { assert(q); QNode* newnode = (QNode*)malloc(sizeof(QNode)); if (newnode == NULL) { perror("malloc fail:"); return; } newnode->data = data; newnode->next = NULL; if (q->rear == NULL) { assert(q->front == NULL); q->front = q->rear = newnode; } else { q->rear->next = newnode; q->rear = newnode; } q->size++; }
分析:
我们是以链表实现队列的,所以每次入队的时候我们都要 malloc 一个 newnode 结点出来,将 newnode->data = data,newnode->next = NULL。
下来就是连接了:
我们要考虑这个结点是否是队列的第一个结点。
1、是队列的第一个结点的话,我们让头尾指针都指向这个结点(q->front = q->rear = newnode);
2、不是队列的第一个结点,我们将尾指针的next赋值为新节点(q->rear->next = newnode;), 再让尾指针指向新节点( q->rear = newnode;);
3、让队列的 size++。
3.3 队头出队列
void QueuePop(Queue* q) { assert(q); assert(!QueueEmpty(q)); //1.一个结点 if (q->front->next == NULL) { free(q->front); q->front = q->rear = NULL; } else//2.多个结点 { QNode* next = q->front->next; free(q->front); q->front = next; } q->size--; }
分析:
出队的时候要考虑队列是否是一个结点:
1、队列中只有一个结点,我们将这一个结点释放掉后(free(q->front)),将头、尾指针都置空(q->front = q->rear = NULL);
2、队列中有多个结点,那我们就将队头的下一个结点先保存起来(next = q->front->next),然后将队头释放掉(free(q->front)),最后让队头指向释放前队头的下一个结点(q->front = next);
3、最后让队列的 size--。
3.4 获取队列头部元素
取队头元素时,我们先要对队列进行判空,如果队列为空,那就不存在队头元素。
QDataType QueueFront(Queue* q) { assert(q); assert(!QueueEmpty(q)); return q->front->data; }
3.5 获取队列尾部元素
取队尾元素时,我们先要对队列进行判空,如果队列为空,那就不存在队尾元素。
QDataType QueueBack(Queue* q) { assert(q); assert(!QueueEmpty(q)); return q->rear->data; }
3.6 获取队列中有效元素个数
我们在创建队列的结构体时,在里面创建了一个变量 size,它专门记录队列中的元素个数,所以在这我们只要返回 q->size就可以了。如果没有定义 size 变量的话,我们遍历一遍队列,用计数器来统计一下个数也是可以的。
int QueueSize(Queue* q) { assert(q); return q->size; }
3.7 检测队列是否为空
3.7.1 int 类型接口
这里我们约定,为空返回非 0,非空返回 0。
int QueueEmpty(Queue* q) { assert(q); if (0 == q->size) return 1; else return 0; }
3.7.2 bool 类型接口
直接判断队头是否为空,队头为空队列就是空。
bool QueueEmpty(Queue* q) { assert(q); return q->front == NULL; }
3.8 销毁队列
销毁我们已经写的很多了,这就直接拿捏。我们先将单链表进行释放,这里有一个注意点,我们要先记下下一个位置,然后释放当前位置,然后将下个位置再交给当前位置来迭代。最后将头、尾指针置空,再将 size 置0。
void QueueDestroy(Queue* q) { assert(q); QNode* cur = q->front; while (cur) { QNode* next = cur->next; free(cur); cur = next; } q->front = q->rear = NULL; q->size = 0; }
4、完整代码
完整代码在代码仓库,入口:C语言: C语言学习的代码,多复习 - Gitee.com