从 0 到 1 学 C 语言队列:链表底层实现(初始化 / 入队 / 出队 / 销毁),代码可直接复用!
队列
1 概念和结构
概念:只允许在⼀端进⾏插⼊数据操作,在另⼀端进⾏删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
队列关键操作说明:
- ⼊队列:进⾏插⼊操作的⼀端称为队尾
- 出队列:进⾏删除操作的⼀端称为队头
队列的底层结构选择:
队列的底层既可以用链表也可以用数组实现,但链表的结构实现更优,数组的结构出队列数组的整体元素要向前挪动一位,效率会非常低。
小提醒:如果对链表不熟悉的可以点击链表
2 队列Queue的实现
2.1 队列头文件Queue.h
的初始设置
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QDataType;
//队列结点结构
typedef struct QueueNode
{int val;struct QueueNode* next;
}QNode;
//队列结构
typedef struct Queue
{QNode* phead;QNode* ptail;int size;
}Queue;
使用
typedef
重命名数据类型typedef int QDataType
使代码具有可扩展性,如果数据类型变化的话直接修改这条typedef
语句就行了。队列Queue
的底层是用链表来进行实现,而链表是用一个个结点QNode
实现。由于队列Queue
对只对其前后末端位置操作,为了方便找到链表的最后一个元素而不用再从头结点遍历,我们使用一个ptail
指针指向链表尾部。并用size
记录链表的大小。
2.2 队列的初始化
void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;pq->size = 0;
}
pq->phead = pq->ptail = NULL
头指针和尾指针都置为NULL
,并将size
也赋值为0
。
2.3 入队列
在此之前先要说明判断队列非空的
QueueEmpty
函数
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;
}
如果队列中
size=0
即没有元素则队列为空。
入队列完整实现:
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc failed!");exit(1);}newnode->val = x;newnode->next = NULL;if (QueueEmpty(pq)){pq->ptail = pq->ptail = newnode;}else{pq->ptail->next = newnode;pq->phead = pq->ptail->next;}pq->size++;
}
第一部分:
用malloc
函数创造结点,并将结点初始化
QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc failed!");exit(1);}newnode->val = x;newnode->next = NULL;
第二部分:
由于队列的底层仍然是链表,所以插入要分为链表为空和链表非空两种情况(用QueueEmpty(pq)
函数判断链表是否为空)
if (QueueEmpty(pq)){pq->ptail = pq->ptail = newnode;}else{pq->ptail->next = newnode;pq->phead = pq->ptail->next;}
第三部分:
将队列的size++
pq->size++;
2.4 出队列
void QueuePop(Queue* pq)
{assert(!QueueEmpty(pq));QNode* temp = pq->phead;pq->phead = pq->phead->next;free(temp);if (pq->phead == NULL){pq->ptail = NULL;}pq->size--;
}
第一部分:
出队列首先要用QueueEmpty
函数判断队列是否为空
assert(!QueueEmpty(pq));
第二部分:
将队列头原位置赋值给temp
,再将头结点移动到下一个结点,并将temp
指向空间释放。
QNode* temp = pq->phead;
pq->phead = pq->phead->next;
free(temp);
第三部分:
如果将最后一个元素出栈了即phead=NULL
,则此时也应该改变tail
为NULL
,并最后将size--
if (pq->phead == NULL)
{pq->ptail = NULL;
}
pq->size--;
2.5 取队头元素
QDataType QueueFront(Queue* pq)
{assert(!QueueEmpty(pq));return pq->phead->val;
}
首先判断队列非空,将头节点
phead
指向的元素val
返回就可以了
2.6 取队尾元素
QDataType QueueBack(Queue* pq)
{assert(!QueueEmpty(pq));return pq->ptail->val;
}
首先判断队列非空,将尾节点
ptail
指向的元素val
返回就可以了
2.7 队列有效数据元素个数
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}
size
就是队列的有效元素个数
2.8 销毁队列
void QueueDestroy(Queue* pq)
{assert(pq);QNode* pcur = NULL;while (pq->phead != NULL){pcur = pq->phead;pq->phead = pq->phead->next;free(pcur);}pq->phead = pq->ptail = NULL;pq->size = 0;
}
当指针
pq->phead
不为NULL
时,一直向后遍历,pcur
记录当前头节点phead
的地址,而phead
指向它的下一个位置,free(pcur)
将先前头节点phead
指向位置空间释放,最后pq->phead = pq->ptail = NULL
将头结点phead
和尾节点ptail
都赋值为NULL
,并将size
置为0