队列------java

        此篇文章用来梳理队列,在维基百科中队列的定义是一种抽象的数据结构,遵循先进先出的操作原则。队列也是线性表的一种,且队列可以用链表、数组、栈实现,队列就像他的名字一样,其结构就像是生活中大家买东西排队付款一样,在没有特殊情况下,人们从队尾入队,付完款的人从队头离开。这个过程也包含我们后面要模拟实现的部分队列操作,若你还是不太理解,请看这幅抽象的图:

图中是一个普通的队列抽象图。在小小队列中还有一种更为特殊的队列,双端队列,其维基百科定义是一种特殊的抽象的数据结构类型,是对队列的扩展,允许在队列的前端或后端添加或删除元素。这点比普通的队列操作数据更为方便,那么如何理解它呢?我们可以把它想象成可上可下的电梯,底楼人们可以上电梯也可以下电梯,到楼上人们可以上电梯也可以下电梯。若还是不能想象,可以看图:

Deque <Intger> queue=new LinkedList<>();

Deque是一个java集合框架中的一个接口继承Queue接口有多个实现类,常见的有ArrayDeque和LinkedList,其中ArrayDeque是基于数组实现,另外一个是基于链表实现的,以上是队列的一些相关概念内容。接下来我们选择要去实现一个普通的队列。

        我们可以先分析用什么实现?1.单链表从头节点出,尾节点入,但是每次入队时要遍历,时间复杂度为O(n),为了降低时间复杂度我们可以采用一个尾标记使时间复杂度降为O(1),出队时要找到其头节点,我们可以采用一个首标记标记头节点,在上面的操作后用链表实现入队、出队的时间复杂度已降为O(1)。你肯定会好奇为什么不从队头入?因为如果从队头入不满足队列的规则,尾入头出,头入 头出与队列先进先出的规则产生矛盾,那么你又会想从队头入队尾出呢?可是你要想队尾如何出呢?单链表的节点没有记录前一个节点的位置,你想删最后一个节点,肯定要从头遍历找到前一个节点但时间复杂度已经为O(n),完全失去队列出队操作时间复杂度为O(1)的高效执行目标。所以,你现在应该知道我为什么不那样做了。此处实现我们就不过多阐述了。

        2.双向链表是一个很好的选择对于实现队列来说,由于它的每一个节点都包含前一个节点的位置、后一个节点的位置,故它的头和尾都可以被当做入队口。不过为了高效的入队或出队,它也要有头标记、尾标记,使入队与出队的时间复杂度为O(1),接下来我们可以详细谈谈它如何实现队列。既然我们已经知道要用双向链表实现那么建好队列的基再建好队列的房。首先,建好构建队列的队元素即双向链表的节点,队元素包含值、前一个节点地址、后一个节点的地址,通过构造方法创建一个队元素。前面已经讨论过要用头、尾标记故此处直接引出构建头节点、尾节点标记,为了随时知道队元素的个数,采用长度标记。如图:

 最基本的元素已搭好,接下来是构建队列,此处我们采用尾插法。在插入时我们需要判队列是否为空,要是队列为空,头标记与尾标记同时指向一个这个节点,否则插入到最后一个节点后面,尾标记的next存入新插入的这个节点,新节点的前驱的指向尾标记指向的最后一个节点,之后移动尾标记使他指向新插入的最后一个节点。插入后长度变长,长度标记加加。代码如图:

在插入操作结束后,就要进行下一个操作删除,根据队列的规则,出队从队头出,那么删除对应从队头删。那么这个删除该如何操作呢?首先判断头标记是否为空,若头标记为空则表示队列为空直接返回null,直接获取队头的值,之后要移动头标记到头标记的下一个,之后再判断头节点是否为空,若为空表示只有一个节点,就不用做过多的处理,若不为空,头标记指向下一个节点时因为是删除,下一个节点的前继设为空,彻底删除。删除操作进行后长度减减。代码如图:

删除和插入后进行,获取队头元素,判队是否空,直接返回。那么如何判空呢?长度为0时就表明为空。队不为空时,直接返回头节点的值。代码如图:

还有获取队头元素,判空后利用链表的头标记对应的值为队头的值即可,代码如图:

以上就是双向的链表实现队列。

        3.采用数组实现队列。可是普通的数组无法实现队列,因为存入数组后,从数组头出队,一直出队,一直入队,会出现数组段前面会出现很多空空间,后面可能会出现越界,故普通数组实现队列的会造成空间的浪费和数组的越界。我们必须要采用特殊数组来解决问题,经过深入思考,采用循环数组就不会浪费很多空间和数组的越界。那么如何循环数组呢?(下标+数组长度-差值)%数组长度,差值是下一次移动到下标与当前下标的差值,这个包含了下标为负的情况。此处不含下标为负的场景(尾标记【当前下标】+1)%数组长度做出循环队列让它插入至最后一个元素时可以又回到数组前段继续插入。我们知道如何构建循环数组后,要思考一个问题我们插入、删除会需要涉及到判空、判满操作,针对循环数组如何判空、判满?同样要使用头标记和尾标记,当队列为空时,头标记和尾标记的指向位置一样队列为空,那么满呢?若不采用任何行动,会出现满和空时头标记和尾标记一样。我们会选择浪费一个空间,当尾标记+1=头标记时表示队列满了。如图:

接下来是构建好队列,先是头标记和尾标记数组,在构造方法处把数组长度加1代表多一个空间。之后进行实现队列的基本操作。1.判满:尾标记加1等于头标记返回true;2.判空:头标记等于尾标记返回true;3.入队列:先判队列是否满队,若满队则直接返回。否则,数组的尾标记值为想要入队的值。循环数组中尾标记移动到下一个空间,等于rear+1)%数组长度。代码如图:

4.出队列:判队列是否为空,为空则直接返回。否则头标记移动到下一个元素的下标。front=(front+1)%数组长度。代码如图所示:

5.获取队头元素,若为空直接返回否则得到数组的头标记所指向的元素。获取队尾元素,队为空,直接返回,否则判尾标记是否是为0,不为0则队尾元素下标为rear-1,为0则队尾元素下标为数组长度减1不为rear-1是防止下标变为负的。根据下标可以找到队尾元素。代码如图:

        接下来,我们要说一些队列的面试题。1.队列实现栈,思路是用两个队列去实现栈的后入先出,以及实现栈的四种基本操作(push()、pop()、getTop()、empty()),模拟入栈操作只要队列1或2不为空就放入k个元素,模拟出栈再把不为空的队列中的k-1个元素放到另一个队列中,剩余的一个元素弹出就是对应的栈顶元素,若要持续出栈,就把不为空队列中的k-2个元素放入另一个空队列中,剩余的就是下一个要出栈的元素,如此往复即可完全出栈。实现代码如图:

        2.用栈模拟实现队列,思路:使用两个栈,模拟入队:把所有的元素放入第一个栈中,模拟出队,判断第二个栈中是否为空,为空则需要把第一个栈中所有元素都放入第二个栈中,取出第二个栈中栈顶元素出栈则对应着队中出队元素,若不为空,表示放入元素在第二个栈中,取第二个栈的栈顶元素对应出队元素即可。实现代码如图:

        这队列一节就说到这里,文中若有误,麻烦告知小编,万分感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值