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

【数据结构和算法】--队列

目录

  • 队列的概念及结构
  • 队列的实现
    • 初始化
    • 入队
    • 出队
    • 其他一些队列函数
  • 小结
  • 队列相关题目

队列的概念及结构

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

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

队列结构联想起来也非常简单,如其名,队列就相当于银行办理业务的柜台前一条长长的队伍,排在队伍前面的人(队头)可以先办理,而在队伍最后面的人(队尾)只能最后办理,如果继续有人来办理业务就只能排在队尾,这样的模式就是队列且遵循队头出队列,队尾入队列的原则。结构大致如下:

在这里插入图片描述

队列的实现

队列的实现同样有两种方式–数组队列,链式队列

  1. 数组队列:
    如果用数组实现队列:这样队头就只能指向下标为0的位置,那么队尾就指向队列最后一个元素的后一个的下标,基于此结构,想要出队操作,时间复杂度就会升高为O(N)且十分不方便。因为每当要将下标为0的元素出队,后面的所有元素就要前移并改变队尾指向。大致如下:

在这里插入图片描述

还有一点就是,如果使用数组队列,在前期有大量数据入队列,动态开辟了很大的空间,到后期可能很多数据都出了队列但动态开辟的空间并不能释放,那就造成了严重的空间浪费。

  1. 链式队列:
    如果使用双向链表: 这与栈为什么不用双向链表实现一个道理,虽然可以实现,且入队/出队的时间复杂度都为O(1)但结构较为复杂,实现起来并不是很实用;
    那么我们这时会选择结构更优得单链表,虽说单链表尾插得时间复杂度是O(N),但我们可以定义一个变量(QNode* ptail)来记录单链表的尾节点,这样每次入队时便不用再遍历单链表找尾节点,时间复杂度也降为O(1)。结构如下:

在这里插入图片描述

这样按需开辟空间,出队列就释放节点,进队列新建节点,也大大减少了空间的浪费。我们便可如下定义队列的每个节点:

typedef int QDataType;
//队列节点
typedef struct QueueNode
{QDataType val;struct QueueNode* next;//链接下一个节点的指针
}QNode;

因为我们要记录队列的头(QNode* phead)和尾(QNode* ptail)(下文称这两个指针为头指针和尾指针),且基本每个函数都要用到这两个变量,甚至一些函数还要修改这两个变量对应的值,那就不得不传二级指针了,这也就大大增加了代码的难度!为了解决这个问题,我们可以定义一个结构体,并将这两个变量放到结构体中,每次函数调用时,只需传这个结构体地址即可
为了方便统计队列里面的节点数,我们通常还会定义一个整型变量size,有元素出队列时size--,入队列时size++
此结构体定义方式如下:

typedef struct Queue
{QNode* phead;QNode* ptail;int size;
}Queue;

初始化

初始时队列中无节点,所以pq->size = 0,头指针(pq->phead = NULL)尾指针(pq->ptail = NULL)都指向空。

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

入队

入队操作首先要新建一个节点(类型为QNode),然后通过尾指针指向的节点链接此节点pq->ptail->next = newnode),并更新尾指针的指向pq->ptail = newnode),将记录节点数的值加1(pq->size++)。
在链接时还要考虑到队列为空的情况,此情况下直接将头/尾指针指向新建的节点(pq->ptail = pq->phead = newnode)。

//入队列
void QueuePush(Queue* pq, QDataType x)
{assert(pq);//新建节点QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("QueuePush()::malloc");return;}//新建的节点初始化newnode->next = NULL;newnode->val = x;//判断,链接if (pq->ptail == NULL){//1.原队列无节点pq->ptail = pq->phead = newnode;}else{//2.原队列已有节点pq->ptail->next = newnode;pq->ptail = newnode;}pq->size++;
}

出队

出队列操作首先要确保队列有节点才能出队,那么断言一下就可(assert(pq->phead);)。需要注意的是出队是头删,可以先定义变量记录头节点QNode* del = pq->phead;),然后再将头指针指向下一个节点phead = phead->next;),最后将原头节点释放free(del);),并将记录节点数的值减1pq->size--)。
当队列只有一个节点时,删除后队尾和队头指针都应该指向空,但上述操作并不能达到此效果,所以要单独判断一下,将尾指针指向空。

//出队列
void QueuePop(Queue* pq)
{assert(pq);//队列不为空assert(pq->phead);QNode* del = pq->phead;//记录头节点pq->phead = pq->phead->next;//头指针重定向free(del);//释放原头节点del = NULL;//队列是否被删空判断if (pq->phead == NULL)pq->ptail = NULL;pq->size--;
}

其他一些队列函数

  1. 队列有效数据个数:
    上面也讲过pq->size记录的就是队列的有效数据个数
//数据个数
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}
  1. 队列是否为空:
    两种判断方法:1.当头指针指向NULL,2.pq->size的值为0
//队列是否为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;//return pq->size == 0;
}
  1. 返回队头数据:
    首先要判断队列不为空,然后返回头指针指向的节点的val即可。
//返回队头数据
QDataType QueueFront(Queue* pq)
{assert(pq);assert(pq->phead);return pq->phead->val;
}
  1. 返回队尾数据:
    同上,首先要判断队列不为空,然后返回尾指针指向的节点的val即可。
//返回队尾数据
QDataType QueueBack(Queue* pq)
{assert(pq);assert(pq->phead);return pq->ptail->val;
}
  1. 释放队列空间:
    遍历队列直到头指针指向空,每遍历一次释放一个节点,最后将头/尾指针指空,pq->size0
//释放
void QueueDetroy(Queue* pq)
{assert(pq);QNode* cur = pq->phead;//遍历,释放while (cur){//记录下一个节点地址QNode* next = cur->next;free(cur);cur = next;}pq->phead = pq->ptail = NULL;pq->size = 0;
}

小结

  • 队列是一种先进先出的结构,与栈相反;
  • 队列是头部删除尾部插入一般使用链表实现,且与栈结构一样不支持随机访问
  • 在实现队列时一般定义两个结构体,一个结构体用来记录每个节点中所需要的值,另一个用来记录队列的队头,队尾和节点数。第二个结构体的使用避免了二级指针,且在函数调用时一般传第二个结构体的地址

队列相关题目

  1. 下面关于栈和队列的说法中错误的是( A B
    A.队列和栈通常都使用链表实现
    B.队列和栈都只能从两端插入、删除数据
    C.队列和栈都不支持随机访问和随机插入
    D.队列是“先入先出”,栈是“先入后出”

这题主要考察对队列和栈的性质的区分,思路如下:

  • A错误:是尾部插入和删除,一般使用顺序表实现,队列是头部删除尾部插入,一般使用链表实现

  • B错误:是后进先出,尾部插入和删除队列是先进先出,尾部插入头部删除

  • C正确:栈只能访问栈顶元素,不支持随机访问,队列也不支持

  • D正确:栈和队列的特性

关于队列还有一个知识点就是循环队列,因其结构复杂就单独拿出来讲。

相关文章:

  • Kubernetes(k8s)集群部署----->超详细
  • Spring Boot学习随笔- 集成JSP模板(配置视图解析器)、整合Mybatis(@MapperScan注解的使用)
  • 企业选CRM系统,这3个关键点你一定不能错过
  • 【摸鱼向】利用Arduino实现自动化切屏
  • python自动化测试实战 —— 自动化测试框架的实例
  • MySQL 报错 You can‘t specify target table for update in FROM clause解决办法
  • Flink 读写 HBase 总结
  • JeecgBoot jmreport/queryFieldBySql RCE漏洞复现
  • ArcGIS pro与SuperMap根据属性自动填充颜色步骤
  • 【JVM入门到实战】(三) 查看字节码文件的工具
  • 结构化并发 ForkJoinPool StructuredTaskScope
  • ExoPlayer架构详解与源码分析(10)——H264Reader
  • 【数据结构】平衡树引入
  • 用23种设计模式打造一个cocos creator的游戏框架----(十四)观察者模式
  • SCT52A40——120V,4A,高频高压侧和低压侧栅极驱动器
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • 10个最佳ES6特性 ES7与ES8的特性
  • css属性的继承、初识值、计算值、当前值、应用值
  • Facebook AccountKit 接入的坑点
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • mysql外键的使用
  • Python 反序列化安全问题(二)
  • text-decoration与color属性
  • vue-cli3搭建项目
  • 初识MongoDB分片
  • 对话:中国为什么有前途/ 写给中国的经济学
  • 对象引论
  • 给github项目添加CI badge
  • 爬虫模拟登陆 SegmentFault
  • 以太坊客户端Geth命令参数详解
  • 用简单代码看卷积组块发展
  • 怎么把视频里的音乐提取出来
  • 怎样选择前端框架
  • (Note)C++中的继承方式
  • (python)数据结构---字典
  • (PyTorch)TCN和RNN/LSTM/GRU结合实现时间序列预测
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (六)vue-router+UI组件库
  • (太强大了) - Linux 性能监控、测试、优化工具
  • (一)u-boot-nand.bin的下载
  • (最完美)小米手机6X的Usb调试模式在哪里打开的流程
  • ... fatal error LINK1120:1个无法解析的外部命令 的解决办法
  • ./indexer: error while loading shared libraries: libmysqlclient.so.18: cannot open shared object fil
  • .\OBJ\test1.axf: Error: L6230W: Ignoring --entry command. Cannot find argumen 'Reset_Handler'
  • .bat批处理(七):PC端从手机内复制文件到本地
  • .net core使用RPC方式进行高效的HTTP服务访问
  • .NET 将混合了多个不同平台(Windows Mac Linux)的文件 目录的路径格式化成同一个平台下的路径
  • .NET 使用 XPath 来读写 XML 文件
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉
  • .NET应用架构设计:原则、模式与实践 目录预览
  • .NET中使用Protobuffer 实现序列化和反序列化
  • .stream().map与.stream().flatMap的使用
  • /etc/X11/xorg.conf 文件被误改后进不了图形化界面