前面写过单端队列,文章链接。
提示:自定义实现的集合框架源码在这里。
单端队列有数组和链表两种实现方式。数组的实现方式相对比链表复杂一些,需要理解数学关系。
接下来,是时候实现双端队列了。
简单介绍
单端队列只能从一端插入元素,另一端取出元素。而双端队列则可以在两端均可以插入和删除元素。
双端队列可以充当单端队列,也可以用于充当栈。
在Java中,LinkedList的内部使用双端链表队列原理实现,而ArrayList的内部使用双端数组队列原理实现。
基本方法
双端队列接口Deque和Queue以及Stack中对应的方法如下:
数组实现
数组实现的方法和单端队列时实现的方式差不多,都是规定了一个head和tail。
head用来表示头元素的位置,或者翻译官方API的话是即将删除和查看的那个元素。
tail用来表示尾元素的下一个位置,或者翻译官方API的话是元素即将被插入的位置。
在这里需要注意的是和单端队列的实现方式不同的是这里核心的数学表达式采用的是逻辑与操作,以此来确定元素的操作位置。
首先数组的初始容量capacity__必须是2的指数倍__,可以是8、16,但不能是10或者20。
以tail为例,在add方法中,使用的数学表达式如下:1tail = (tail + 1) & (capacity - 1);
这样的效果和下面的表达式是等价的:1tail = (tail + 1) % capacity;
这是为什么呢?
因为capacity为2的指数倍,比如8。
其二进制的表示为1000,那么capacity - 1为7,对应的二进制的表示为0111,其低位均为1。
假设tail是4,那么4&7就为4;若tail是7,那么tail+1是8,而8&7为0,则tail下一个插入的位置是0,结果是正确的。
如果是head进行addFirst操作的话,那么就是head-1,head-1可能是负数,使用(head-1)&(capacity-1)可以解决负数的问题。
如果是使用求模符号,则为(head+capacity-1)%capacity。
使用逻辑运算的速度一般快于四则运算的速度,所以使用逻辑与运算操作是更好的。
链表实现
使用链表实现,不同与单端队列,链表节点需要同时记录前一个节点和后一个节点。
单端队列的实现,我采用的做法是头结点只是起到引导作用,牵住整个链表,没有实际的存储意义,这样操作起来会比较方便。
而双端队列所有的节点都是有意义的。
在双端队列的增删改的操作中,需要仔细考虑被操作节点与前后节点(即使没有)的关系。
源码下载
源码我放在了[这里],这是自己实现Java集合框架的一部分。欢迎提出宝贵意见。