博主秋招提前批已拿百度、字节跳动、拼多多、顺丰等公司的offer,可加微信:pcwl_Java 一起交流秋招面试经验,可获得博主的秋招简历和复习笔记。
一、队列的基本概念
1、定义
队列是一种先进先出的线性表。它只允许在表的前端进行删除操作,而在表的后端进行插入操作,具有先进先出、后进后出的特点。进行插入操作的一端成为队尾(tail),进行删除操作的一端称为队头(head)。当队列中没有元素时,则称之为空队列。
在队列中插入一个元素称为入队,从队列中删除一个元素称为出队。因为队列只允许在队尾插入元素,在队头删除元素,所以队列又称为先进先出(FIFO-first in first out)线性表。其实队列这种数据结构的特性,让我们很容易就想到了平时生活中我们排队场景,真是无处不在啊。。。图书馆、食堂、餐厅、公交车。。。
而在程序框架方面也有很多应用:最常见的就是各种“池“,比如:线程池、数据库连接池,分布式中的消息队列等待。都体现出一种公平的思想,即先到的先得。
2、队列和栈
其实队列和栈有很多相似的地方,栈的两个基本操作:压栈(push)和弹栈(pop),而队列的最基本的两个操作是:入队(enqueue())和出队(dequeue())。因此队列和栈一样,也是一种操作受限的数据结构。
3、队列的入队和出队操作(以数组为例)
入队操作enqueue:每次从队尾插入元素,时间复杂度:O(1)
出队操作dequeue:每次从队头删除元素,时间复杂度:O(n)
但是可以发现,数组实现的队列有很大的不足,每次从数组头部删除元素后,需要将头部的所有元素往队首移动一个位置,这是一个时间复杂度为O(n)的操作。
你可能会想到一种办法:每进行一次出队操作,就将队首的的标志往队尾移动一个内存空间,这样就不用进行数据搬移了,但是很明显这样又会产生一个很大的弊端,当队尾标志tail移动到最右边时,即使数组中还有空闲的内存空间,也无法往队列中添加数据了,不能很好的利用内存空间。
你可能还会想到一种办法:和JVM垃圾回收类似的思想,在出队操作的时候,我们不用先搬移数据,当没有空间空间,无法插入新数据的时候,在进行一次整体的数据搬移操作,这样可以将出队操作的时间复杂度降低为O(1),但是入队操作时,需要先判断队列中是否有空间的内存空间,如果有,直接入队,但是如果没有,则需要将队列中的数据进行一次整体的搬移,这样时间复杂度就为O(n)了,显然也不理想。其实现代码见文末:动态队列的数组实现
循环队列很好的解决了这个问题。见下文......
4、循环队列
从上面的数组实现的队列来看,其删除操作的时间复杂度为O(n),而我们希望得到时间复杂度都为O(1)的插入和删除操作,所以循环队列就很好的符合了我们的标准。
所谓的循环队列,就是长的像一个环,原本的队列是由头有尾的,是一条直线,我们现在将首位相连,就形成了一个环。如下图所示: