文章目录
前言
本文章均基于C语言实现,如有不足,欢迎指正
以下是本篇文章正文内容,下面案例可供参考
一、队列的概念及其结构
1.定义
队列(Queue):只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有 先进先出 FIFO (First In First Out) 的特性
2.术语
入队:向队列中插入元素的操作为 入队
队尾(Rear):进行插入操作的一端称为 队尾
出队:向队列中删除元素的操作为 出队
队头(Front):进行删除操作的一端称为 队头
3.实际应用
实际中,表示公平排队的地方均可以用到 队列
比如,银行的 抽号机 、键盘输入循环缓冲区问题
二、队列的实现
队列也可以数组和链表的结构实现,使用 链表 的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低
1.队列的基本操作
队列的基本操作有如下几点:
初始化队列
队尾入队列
队头出队列
获取队列头部元素
获取队列队尾元素
获取队列中有效元素个数
检测队列是否为空
销毁队列
2.代码实现
注:以下代码均以链表结构实现
2.1 创建队列
下列将 QDataType 进行宏定义,表示该队列 元素类型 ,方便将来新队列元素类型的替换
Qnode 用以存储 数据 和 下一结点
front 表示 队头
rear 表示 队尾
代码如下:
//进行宏定义
typedef QDataType int
// 链式结构:表示队列
typedef struct QListNode
{
struct QListNode* next;
QDataType data;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* front; //表示队头
QNode* rear; //表示队尾
}Queue;
2.2 初始化队列
assert 用以判断所传指针是否为 NULL ,多运用 assert 有利于帮助快速查错
如图
void QueueInit(Queue* q)
{
assert(q);
q->front = NULL;
q->rear = NULL;
}
2.3 队尾入队列
由于此处队列使用 链表 结构,因此尾插新的元素,需使用 malloc() 函数开辟新的空间,值得注意的是, malloc() 函数可能开辟失败,返回 NULL ,需注意该点,避免 野指针 的使用。
另外,如果 队头 为 NULL ,则须将队头也赋值为该结点
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode)
{
if (q->rear)
q->rear->next = newnode;
q->rear = newnode;
newnode->next = NULL;
newnode->data = data;
}
else
{
printf("malloc failed\n");
exit(-1);
}
if (q->front == NULL)
q->front = newnode;
}
2.4 队头出队列
1.倘若该队列为 空队列 ,则无法出队列
2.值得注意的是,倘若该 队列 仅有一个元素,则 队尾 须置为 NULL
原因:当 队头 释放此处空间后,该地址便还给计算机,但 队尾 依旧存储该地址,后续对该地址进行操作,即为 野指针的非法访问
3.下列代码中 if() 语句 用以判断该队列是否仅有一个元素,若 p 为 NULL,则该队列仅有一个元素
void QueuePop(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
QNode* p = q->front->next;
if (p == NULL)
{
free(q->front);
q->front = NULL;
q->rear = NULL;
}
else
{
free(q->front);
q->front = p;
}
}
2.5 获取队列头部元素
只需判断是否为 空队列 即可
QDataType QueueFront(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->front->data;
}
2.6 获取队列队尾元素
只需判断是否为 空队列 即可
QDataType QueueRear(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->rear->data;
}
2.7 获取队列中有效元素个数
不考虑效率,若非频繁调用,即可创建 n 用以记录元素个数
若频繁调用,可在 Queue 中创建 size
int QueueSize(Queue* q)
{
assert(q);
int n = 0;
QNode* cur = q->front;
while (cur)
{
n++;
cur = cur->next;
}
return n;
}
2.8 检测队列是否为空
这里会用到 bool ,不清楚的小伙伴可以用 int 代替
如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q)
{
assert(q);
if (q->rear == NULL)
return true;
return false;
}
2.9 销毁队列
需创建临时变量 p 保存下一个结点
void QueueDestroy(Queue* q)
{
assert(q);
QNode* cur = q->front;
while (cur)
{
QNode* p = cur->next;
free(cur);
cur = p;
}
}
附上运行图
三、队列的经典问题-设计循环队列
题目链接: 设计循环队列(点击即可跳转)
1. 题目描述:
2. 解题思路:
我们设计头尾两个指阵来实现
假设规定 头尾指针相同 ,则 队列为 满
但是,这样又出现一个问题,当 队列为 空 时, 头尾指针也相同 ,无法判断队列状态,如图
因此,我们需要设计新方法,区分队列是否为为 空 或为 满
最简单的方法就是, 额外多出一个空间
倘若指针 相同 ,则为 空
若头尾指针 间隔为1 ,则为 满 。如图
3. 代码实现:
思路有了,就是代码和细节问题了
由于是循环队列,则空间大小一定,此处无需使用链表结构
假设队列有效元素个数为 K ,则开辟 K+1 个元素大小的空间
另外,在写时想出来一个偷懒的写法,分享给你们
注:以下代码仅供参考,非最优解
typedef struct {
int* queue;
int front;
int rear;
int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* q = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
int* p = (int*)malloc((k+1) * sizeof(int));
q-> queue = p;
q-> front = 0;
q-> rear = 0;
q-> k = k;
return q;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
assert(obj);
if(obj -> front == obj -> rear)
return true;
return false;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
assert(obj);
if((obj -> rear + 1) % (obj -> k +1)== obj -> front)
return true;
return false;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
assert(obj);
if((obj->rear+1)%(obj->k+1) == obj->front)
{
return false;
}
obj->queue [ obj -> rear] = value;
obj ->rear = (obj -> rear + 1) % (obj -> k + 1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
assert(obj);
if(obj -> front == obj -> rear)
return false;
obj ->front = (obj -> front + 1) % (obj -> k + 1);
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
assert(obj);
if(myCircularQueueIsEmpty(obj))
return -1;
return *(obj ->queue + obj -> front);
}
int myCircularQueueRear(MyCircularQueue* obj) {
assert(obj);
if(myCircularQueueIsEmpty(obj))
return -1;
return obj ->queue [(obj -> rear + obj -> k) % (obj -> k + 1)];
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->queue);
free(obj);
}
/**
* Your MyCircularQueue struct will be instantiated and called as such:
* MyCircularQueue* obj = myCircularQueueCreate(k);
* bool param_1 = myCircularQueueEnQueue(obj, value);
* bool param_2 = myCircularQueueDeQueue(obj);
* int param_3 = myCircularQueueFront(obj);
* int param_4 = myCircularQueueRear(obj);
* bool param_5 = myCircularQueueIsEmpty(obj);
* bool param_6 = myCircularQueueIsFull(obj);
* myCircularQueueFree(obj);
*/
附上运行图:
以上就是本次所带来的全部内容,如有帮助,可以点赞支持一波哦~