当前位置: 首页 > news >正文

数据结构初阶:详解栈和队列(下)——队列

🔥个人主页:胡萝卜3.0

🎬作者简介:C++研发方向学习者

📖个人专栏:  《C语言》《数据结构》 《C++干货分享》

⭐️人生格言:不试试怎么知道自己行不行

目录

(二)队列

2.1 队列的概念和结构

2.1.1 概念

2.1.2 结构

2.2 队列的实现

2.1.1 队列的结构

2.1.2 队列的初始化

2.1.3 入队——队尾

2.1.4 判断队列是否为空

2.1.5 出队

2.1.6 取队头数据

2.1.7 取队尾数据

2.1.8 有效数据个数

2.1.9 队列的销毁

2.3 完整代码

2.4 代码优化

2.4.1 优化方案

2.4.2 优化后的完整代码


(二)队列

2.1 队列的概念和结构
2.1.1 概念

概念:只允许在一端进行插入数据操作,在另⼀端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)

入队列:进行插入操作的一端称为队尾

出队列:进行删除操作的一端称为队头

2.1.2 结构

为了节省记忆成本,我们可以把队列理解为饮水机

用数组来实现队列,还是用链表来实现队列?

那么有uu就要问了,双向链表所有的复杂度都是O(1)啊,用双向链表不是更高效吗?

int  data
*nest
*prev

在结构体里面,像单链表只有上图中前两个成员,大概是8个字节,如果是双向链表就是三个成员,3*4有12个字节,这里空间就更大了!因此一般情况下,我们轻易不会用双向链表这种数据结构,要用也是用单链表,所以这里我们就不进一步分析双向链表了。

所以接下来我们还是要在数组和链表里面去二选一。

从上图可以看出,这里单纯从复杂度角度我们没有办法做到两全其美。如果我们使用当前两种数据结构的任意一个,我们发现要么删除时间复杂度是O(N),要么插入时间复杂度是O(N)。

那么我们能不能做一些优化呢?

(1)假如用数组进行时间复杂度优化

比如说我们要用数组来实现队列,我们能不能去优化一下,哪怕我额外增加一些成本,但是这个成本非常低,我也能实现删除数据时间复杂度为O(1),行不行呢?

(1)像删除数据,比如我们在队头删除数据,所有的数据都要往前(队头)移,你能不能说我们删除数据但是不往前移呢?是不是也不行的呀,如果你不往前移,会影响我们后面插入数据;

(2)如果把队头和队尾互换一下呢?也不能。这是头插呀,前面如果不留位置,怎么头插呀。

这样看来,用数组来进行时间复杂度的优化好像不太现实。

(2)假如用链表进行时间复杂度优化

链表就可以了——

我们可不可以再定义一个指针,这个成本可以接受吧,我们直接在ptail后面去插入,插入的时间复杂度就变成O(1)了,是不是解决了这个问题。

注意事项

我们这里知道用链表实现队列更好是不是说用数组无法实现队列啊?话可不能这么说哦!数组的底层一定是链表吗?不一定,要看你怎么实现,只不过我们用链表来实现成本更低一些。

2.2 队列的实现

2.1.1 队列的结构

通过上面的学习,我们知道队列中应该包含两个指针,一个指针指向头结点,一个指针指向尾节点。

那这两个指针是什么类型的呢?

我们知道在我们此时实现的队列是由链表实现的,那是不是就是说这个phead和ptail是一个结点类型,这样我们就可以知道队列的完整结构了。

//队列的结构
typedef int QDataType;
typedef struct QueueNode
{QDataType data;//存储数据struct QueueNode* next;//保存下一个节点的地址
}QueueNode;
typedef struct Queue
{QueueNode* phead;//指向头结点的指针QueueNode* ptail;//指向尾节点的指针
}Queue;
2.1.2 队列的初始化

刚开始时,队列中没有任何数据,phead和ptail都为NULL

queue.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//队列的结构
typedef int QDataType;
typedef struct QueueNode
{QDataType data;//存储数据struct QueueNode* next;//保存下一个节点的地址
}QueueNode;
typedef struct Queue
{QueueNode* phead;//指向头结点的指针QueueNode* ptail;//指向尾节点的指针
}Queue;
//初始化
void QueueInit(Queue* pq);

queue.c

//初始化
void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include"queue.h"
void test01()
{Queue q;QueueInit(&q);//入队QueuePush(&q, 1);QueuePush(&q, 2);QueuePush(&q, 3);QueuePush(&q, 4);QueuePush(&q, 5);QueuePush(&q, 6);QueuePrint(&q);
int main()
{test01();return 0;
}
2.1.3 入队——队尾

通过上图,我们就可以直接写出代码

//创建结点空间
QueueNode* QueueBuyNode(QDataType num)
{QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = num;newnode->next = NULL;return newnode;
}
//入队
void QueuePush(Queue* pq, QDataType num)
{assert(pq);//为num创建结点空间QueueNode* newnode = QueueBuyNode(num);pq->ptail->next = newnode;pq->ptail = pq->ptail->next;
}

但是,不知道有没有uu发现这里存在一个问题,刚开始时,phead和ptail都为空结点,也就是说,队列是一个空队列,此时如果我们直接入队列,会对空指针进行解引用操作,这是不允许的,所以我们需要特殊处理一下

(1)队列不为空:

(2)队列为空:

//创建结点空间
QueueNode* QueueBuyNode(QDataType num)
{QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = num;newnode->next = NULL;return newnode;
}
//入队
void QueuePush(Queue* pq, QDataType num)
{assert(pq);//为num创建结点空间QueueNode* newnode = QueueBuyNode(num);//如果队中没有数据if (pq->phead == NULL){pq->phead = pq->ptail = newnode;pq->size++;}else{pq->ptail->next = newnode;pq->ptail = pq->ptail->next;}
}

有了入队操作,那肯定会有出队操作,在进行出队操作之前,我们先来想一个问题,如果队列为空,我们还能进行出队操作吗?肯定是不行的,队列都为空了,就无法出队了。

2.1.4 判断队列是否为空

当phead为空时,可以说明队列为空

//判断队列是否为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}
2.1.5 出队

出队操作是对头结点进行出队操作,头结点时最先进入队列的,所以需要优先出队

根据上图,我们可以快速写出代码:

这个代码写完了吗?如果队列中只有一个数据

如果我们还按照上面的操作,那么ptail就成了野指针,这是一种很危险的操作,所以我们需要特殊处理一下。

queue.c

//出队
//出的是头结点数据
void QueuePop(Queue* pq)
{assert(!QueueEmpty(pq));if (pq->phead == pq->ptail){free(pq->phead);pq->phead = pq->ptail = NULL;}else{QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include"queue.h"
void test01()
{Queue q;QueueInit(&q);//入队QueuePush(&q, 1);QueuePush(&q, 2);QueuePush(&q, 3);QueuePush(&q, 4);QueuePush(&q, 5);QueuePush(&q, 6);QueuePrint(&q);//出队QueuePop(&q);QueuePrint(&q);
}
int main()
{test01();return 0;
}
2.1.6 取队头数据

取队头数据是一个很简单的操作,直接返回对头节点中的数据即可,前提是队列不为空

//取队头数据
QDataType QueueFront(Queue* pq)
{assert(!QueueEmpty(pq));return pq->phead->data;
}
2.1.7 取队尾数据

取队尾数据是一个很简单的操作,直接返回对尾节点中的数据即可,前提是队列不为空

//取队尾数据
QDataType QueueBack(Queue* pq)
{assert(!QueueEmpty(pq));return pq->ptail->data;
}
2.1.8 有效数据个数

遍历队列,然后计数即可

//队列有效数据个数
int QueueSize(Queue* pq)
{assert(!QueueEmpty(pq));QueueNode* pcur = pq->phead;int size = 0;while (pcur != NULL){size++;pcur = pcur->next;}return size;
}

时间复杂度:O(N)

2.1.9 队列的销毁

//销毁队列
void QueueDestory(Queue* pq)
{assert(pq);QueueNode* pcur = pq->phead;while (pcur){QueueNode* next = pcur->next;free(pcur);pcur = next;}pq->phead = pq->ptail = NULL;
}
2.3 完整代码

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;//保存下一个节点的地址
}QueueNode;
typedef struct Queue
{QueueNode* phead;//指向头结点的指针QueueNode* ptail;//指向尾节点的指针
}Queue;
//初始化
void QueueInit(Queue* pq);
//入队
void QueuePush(Queue* pq, QDataType num);
//出队
void QueuePop(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);
//打印
void QueuePrint(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
//有效数据个数
int QueueSize(Queue* pq);
//销毁队列
void QueueDestory(Queue* pq);

queue.c

#define _CRT_SECURE_NO_WARNINGS
#include"queue.h"
//初始化
void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;
}
//创建结点空间
QueueNode* QueueBuyNode(QDataType num)
{QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = num;newnode->next = NULL;return newnode;
}
//入队
void QueuePush(Queue* pq, QDataType num)
{assert(pq);//为num创建结点空间QueueNode* newnode = QueueBuyNode(num);//如果队中没有数据if (pq->phead == NULL){pq->phead = pq->ptail = newnode;}else{pq->ptail->next = newnode;pq->ptail = pq->ptail->next;}
}
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}
//出队
//出的是头结点数据
void QueuePop(Queue* pq)
{assert(!QueueEmpty(pq));if (pq->phead == pq->ptail){free(pq->phead);pq->phead = pq->ptail = NULL;}else{QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}
}
//打印
void QueuePrint(Queue* pq)
{QueueNode* pcur = pq->phead;while (pcur){printf("%d ", pcur->data);pcur = pcur->next;}printf("\n");
}
//取队头数据
QDataType QueueFront(Queue* pq)
{assert(!QueueEmpty(pq));return pq->phead->data;
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{assert(!QueueEmpty(pq));return pq->ptail->data;
}
//队列有效数据个数
int QueueSize(Queue* pq)
{assert(!QueueEmpty(pq));QueueNode* pcur = pq->phead;int size = 0;while (pcur != NULL){size++;pcur = pcur->next;}return size;
}
//销毁队列
void QueueDestory(Queue* pq)
{assert(pq);QueueNode* pcur = pq->phead;while (pcur){QueueNode* next = pcur->next;free(pcur);pcur = next;}pq->phead = pq->ptail = NULL;
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include"queue.h"
void test01()
{Queue q;QueueInit(&q);//入队QueuePush(&q, 1);QueuePush(&q, 2);QueuePush(&q, 3);QueuePush(&q, 4);QueuePush(&q, 5);QueuePush(&q, 6);printf("入队:");QueuePrint(&q);//出队printf("出队: ");QueuePop(&q);QueuePrint(&q);//取队头数据printf("队头: %d\n", QueueFront(&q));//取队尾数据printf("队尾: %d\n", QueueBack(&q));//有效数据个数printf("size: %d\n", QueueSize(&q));//销毁队列QueueDestory(&q);
}
int main()
{test01();return 0;
}

2.4 代码优化
2.4.1 优化方案

前面所有的方法的实现,时间复杂度都是O(1),唯独最后【获取队列有效元素个数】这里时间复杂度变成了O(N),试想一下:如果存在大量的队列有效元素个数——一万个、五万个、十万个数据,那我们每次调用这个方法都要把队列给遍历一遍,这个对于程序的性能来说是不是非常非常差啊,本身这就是一个底层的代码,一个底层的代码时间复杂度都降不到O(1),我们自己的程序里面如果还涉及到循环,那这里程序的执行效率就会非常的低。

我们有没有什么办法可以把队列中的有效元素个数给处理一下呢?可以。

我们定义一个size,用来记录队列中有效数据个数。

对于插入数据的地方,size++;对于删除数据的地方,size--。

2.4.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;//保存下一个节点的地址
}QueueNode;
typedef struct Queue
{QueueNode* phead;//指向头结点的指针QueueNode* ptail;//指向尾节点的指针int size;//有效数据个数
}Queue;
//初始化
void QueueInit(Queue* pq);
//入队
void QueuePush(Queue* pq, QDataType num);
//出队
void QueuePop(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);
//打印
void QueuePrint(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
//有效数据个数
int QueueSize(Queue* pq);
//销毁队列
void QueueDestory(Queue* pq);

queue.c

#define _CRT_SECURE_NO_WARNINGS
#include"queue.h"
//初始化
void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;pq->size = 0;
}
//创建结点空间
QueueNode* QueueBuyNode(QDataType num)
{QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = num;newnode->next = NULL;return newnode;
}
//入队
void QueuePush(Queue* pq, QDataType num)
{assert(pq);//为num创建结点空间QueueNode* newnode = QueueBuyNode(num);//如果队中没有数据if (pq->phead == NULL){pq->phead = pq->ptail = newnode;++pq->size;}else{pq->ptail->next = newnode;pq->ptail = pq->ptail->next;++pq->size;}
}
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}
//出队
//出的是头结点数据
void QueuePop(Queue* pq)
{assert(!QueueEmpty(pq));if (pq->phead == pq->ptail){free(pq->phead);pq->phead = pq->ptail = NULL;pq->size--;}else{QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next;pq->size--;}
}
//打印
void QueuePrint(Queue* pq)
{QueueNode* pcur = pq->phead;while (pcur){printf("%d ", pcur->data);pcur = pcur->next;}printf("\n");
}
//取队头数据
QDataType QueueFront(Queue* pq)
{assert(!QueueEmpty(pq));return pq->phead->data;
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{assert(!QueueEmpty(pq));return pq->ptail->data;
}
//队列有效元素个数
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}
//销毁队列
void QueueDestory(Queue* pq)
{assert(pq);QueueNode* pcur = pq->phead;while (pcur){QueueNode* next = pcur->next;free(pcur);pcur = next;}pq->phead = pq->ptail = NULL;
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include"queue.h"
void test01()
{Queue q;QueueInit(&q);//入队QueuePush(&q, 1);QueuePush(&q, 2);QueuePush(&q, 3);QueuePush(&q, 4);QueuePush(&q, 5);QueuePush(&q, 6);printf("入队:");QueuePrint(&q);//出队printf("出队: ");QueuePop(&q);QueuePop(&q);QueuePrint(&q);//取队头数据printf("队头: %d\n", QueueFront(&q));//取队尾数据printf("队尾: %d\n", QueueBack(&q));//有效数据个数printf("size: %d\n", QueueSize(&q));//销毁队列QueueDestory(&q);
}
int main()
{test01();return 0;
}

运行一下——

我们成功地把【获取队列有效元素个数】部分的时间复杂度也降到O(1)了。

http://www.dtcms.com/a/362231.html

相关文章:

  • 并发编程--线程池(1)线程池概念 Java 线程池体系(Executor、ThreadPoolExecutor、Executors)
  • resnet网络
  • 甲烷浓度时空演变趋势分析与异常值计算(附下载脚本)
  • 洛谷 P5836 [USACO19DEC] Milk Visits S-普及/提高-
  • 基于MCP架构的OpenWeather API服务端设计与实现
  • jetson开发板Ubuntu系统Docker中使用 MySQL 数据库详解-安装与配置指南
  • Python上下文管理器与资源管理
  • 基于51单片机停车场车位引导系统设计
  • 四个典型框架对比
  • 软考-操作系统-错题收集(2)文件系统的多级索引结构
  • 【重学MySQL】九十七、MySQL目录结构与文件系统解析
  • 二叉树核心操作知识点整理
  • 大模型微调显存内存节约方法
  • Java实现的IP4地址合法判断新思路
  • GPT - 5 技术前瞻与开发者高效接入路径探索​
  • 高性能客服系统源码实现
  • 文件上传漏洞基础及挖掘流程
  • 2013 NeuralIPS Translating Embeddings for Modeling Multi-relational Data
  • JAVA后端开发——MyBatis 结合 MySQL JSON 类型查询详解
  • vue组件中实现鼠标右键弹出自定义菜单栏
  • 智慧交通时代,数字孪生为何成为关键力量?
  • Map接口
  • 基于若依框架前端学习VUE和TS的核心内容
  • 手搓3D轮播图组件以及倒影效果
  • 基于STM32的ESP8266连接华为云(MQTT协议)
  • leetcode46.全排列
  • java web 练习 简单增删改查,多选删除,包含完整的sql文件demo。生成简单验证码前端是jsp
  • (Mysql)MVCC、Redo Log 与 Undo Log
  • C#知识学习-012(修饰符)
  • Python OpenCV图像处理与深度学习:Python OpenCV边缘检测入门