一、 ArrayDeque集合特点
底层数据结构:数组
一个基于可变长度数组实现的无界双端队列。不允许null元素。
可以当作双端队列使用 也可以当作普通队列使用 还可以当作栈使用
*普通队列数组实现:从尾部添加元素从头部删除元素
*双端队列接口。删除元素时可以从头部或者尾部进行。添加元素时也可以从头部或者尾部进行
*双端队列肯定是能够实现普通队列的功能的 ------》调用双端队列的尾部添加的方法进行添加元素
调用双端队列头部删除的方法进行删除元素。
*双端队列还可以实现栈的功能 ————》只调用双端队列从头部进行添加和删除的方法
ArrayDeque 当作栈使用性能比Stack要高。因为标记了头尾,更快的出栈和入栈
实现接口 — 》 特点
Collection : 存储单值。
Queue : 队列接口。具有队列先入先出的特点。
Deque: 双端队列接口。删除元素时可以从头部或者尾部进行。添加元素时也可以从头部或者尾部进行。
Cloneable,:可克隆
Serializable:可序列化
Iterator :可以使用迭代器的方式进行迭代。 既可以从前向后遍历 也可以从后往前遍历
ArrayDeque 当作普通队列使用性能比LinkedList要高。 LinkedList底层结构是双向链表
ArrayDeque和LinkedList相比有什么优势?
ArrayDeque通常来说比LinkedList更高效,因为可以在常量时间通过序号对元素进行定位,并且省去了对元素进行包装的空间和时间成本。增删改查时LinkedList更高效
二、ArrayDeque的使用方法
注意:
1)底层是环状数组,增加删除时下标值格外注意 例如:head =(head -1)&(element .length -1);哈希冲突的线性探测法处理时也是这种方式来计算下标。
2)扩容时 二倍扩容 为保证与(element.length-1)的按位与操作 注意扩容会不会超出整数范围
if (initialCapacity < 0)
initialCapacity >>>= 1;
其实它是为了防止进行这一波操作之后,得到了负数,即原来第31位为1,得到的结果第32位将为1,第32位为符号位,1代表负数,这样的话就必须回退一步,将得到的数右移一位(即2 ^ 30)。
3)注意头尾的更新和头尾的物理结构*
1、当作双端队列队列操作:
两端都可以添加和删除 着重关注方法的返回值
1.添加元素
addFirst(E e)在数组前面添加元素 如果添加元素为空返回异常
offerFirst(E e) 在数组前面添加元素,并返回是否添加成功 如果添加元素为空返回异常
addLast(E e)在数组后面添加元素 如果添加元素为空返回异常
offerLast(E e) 在数组后天添加元素, 并返回是否添加成功 如果添加元素为空返回异常
/**
* 在数组前面添加元素,并返回是否添加成功 如果添加元素为空返回异常
* addFirst
* @param e
* @return
*/
public boolean offerFirst(E e){
if(e==null ){
throw new NullPointerException() ;
}
head =(head -1)&(element .length -1);//更新头部
element [head ]=e;
if(head ==tail ){
//扩容 (成环 头尾相遇)
doubleCapacity() ;
}
return true ;
}
/**
* 在数组尾部添加元素,并返回是否添加成功 如果添加元素为空返回异常
* addLast
* @param e
* @return
*/
public boolean offerLast(E e){
if(e==null ){
throw new NullPointerException() ;
}
element [tail]=e;
tail =(tail +1)&(element .length -1);
if(tail ==head ){
//扩容 (成环,头尾相遇)
doubleCapacity();
}
return true ;
}
/**
* 扩容操作 二倍扩容 为保证与(element.length-1)的按位与操作
*/
private void doubleCapacity() {
int h=head;//head=tail 保存坐标
int n=element .length ;//保存原数组的长度 也就是元素的个数
int m=n-h;//从头部下标到数组的最后一个下标(element-1)共有多少个元素
int newCapacity=n<<1;//二倍扩容
if(newCapacity <0){//当扩容超出整型范围,变为负数
throw new IllegalStateException("sorry,Deque is too big");
}
Object []newElement=new Object[newCapacity ];
System .arraycopy(element ,h,newCapacity ,0,m);//先将旧数组的一部分(从头部下标到数组最后一个下标的值复制到新数组,从0号下标开始)
System .arraycopy(element ,0,newCapacity ,m,h);//再将旧数组的剩余部分(从0到头部下标的元素复制到新数组,从上次复制的末尾开始)
element = (E[]) newElement;
head =0;//更新头部
tail =n;//更新尾部,就是旧数组的长度(下标+1)
}
2.删除元素
removeFirst()删除第一个元素,并返回删除元素的值,如果元素为null,将抛出异常
pollFirst()删除第一个元素,并返回删除元素的值,如果元素为null,将返回null
removeLast()删除最后一个元素,并返回删除元素的值,如果为null,将抛出异常
pollLast()删除最后一个元素,并返回删除元素的值,如果为null,将返回null
removeFirstOccurrence(Object o) 删除第一次出现的指定元素
removeLastOccurrence(Object o) 删除最后一次出现的指定元素
/**
* 删除头部 并返回删除元素的值 若头部为空返回空
* @return
*/
public E pollFirst(){
int h=head ;
E result=element [h];
if(result ==null ){
return null ;
}
element [h]=null;
head =(head +1)&(element .length -1);//更新头部
return result ;
}
/**
* 删除最后一个元素 并返回该元素的值,如果钙元素为空,返回null
* @return
*/
public E pollLast(){
int t=(tail-1)&(element .length -1);//因为之前增加尾部时将tail后移,所以要取到尾部元素,应该左移
E result=element [t];
if(result ==null ){
return null ;
}
element [t]=null;
tail =t;//更新尾部
return result ;
}
/**
* 删除第一次出现o的元素
* 遍历查找(&运算时负数以补码形式进行运算) 负数的补码就是反码加一
* @param o
* @return
*/
public boolean removeFirstOccurrence(Object o){
if(o==null ){
return false ;
}
int mask=element .length -1;
int i=head ;
E x;
while( (x=element [i])!=null){
if(x.equals(o) ){
//删除
return true ;
}
i=(i+1)&mask ;
}
return false ;
}
/**
* 删除最后一次出现o的元素
* 为了避免代码重复,将i=tail改为int i=(tail -1 )&mask 因为是环状,所以都会遍历到
* @param o
* @return
*/
public boolean removeLastOccurrence(Object o){
if(o==null ){
return false ;
}
int mask=element .length -1;
int i=(tail -1 )&mask ;
E x;
while( (x=element [i])!=null){
if(x.equals(o) ){
//删除
return true ;
}
i=(i-1)&mask ;
}
return false ;
}
3.获取元素
getFirst() 获取第一个元素,如果没有将抛出异常
getLast() 获取最后一个元素,如果没有将抛出异常
peekFirst(): 获取第一个元素,但是不移除;
peekLast(): 获取最后一个元素,但是不移除;
2、当普通队列时
尾部添加 头部删除 获取元素也是获取头部第一个元素 源码实现直接 调用双端队列的有关方法
add(E e) 在队列尾部添加一个元素
offer(E e) 在队列尾部添加一个元素,并返回是否成功
remove() 删除队列中第一个元素,并返回该元素的值,如果元素为null,将抛出异常(其实底层调用的是removeFirst())
poll() 删除队列中第一个元素,并返回该元素的值,如果元素为null,将返回null(其实调用的是pollFirst())
element() 获取第一个元素,如果没有将抛出异常
peek() 获取第一个元素,如果返回null
3、当栈时
头部添加 头删除 源码实现同样直接调用写好的有关方法
push(E e) 栈顶添加一个元素
pop(E e) 移除栈顶元素,如果栈顶没有元素将抛出异常
三、使用环境
在很多场景下可以用来代替LinkedList,可以用做队列或者栈。
卡拉兹(Callatz)猜想:
对任何一个自然数n,如果它是偶数,那么把它砍掉一半;如果它是奇数,那么把(3n+1)砍掉一半。这样一直反复砍下去,最后一定在某一步得到n=1。当我们验证卡拉兹猜想的时候,为了避免重复计算,可以记录下递推过程中遇到的每一个数。例如对n=3进行验证的时候,我们需要计算3、5、8、4、2、1,则当我们对n=5、8、4、2进行验证的时候,就可以直接判定卡拉兹猜想的真伪,而不需要重复计算,因为这4个数已经在验证3的时候遇到过了,我们称5、8、4、2是被3“覆盖”的数。我们称一个数列中的某个数n为“关键数”,如果n不能被数列中的其他数字所覆盖。
现在给定一系列待验证的数字,我们只需要验证其中的几个关键数,就可以不必再重复验证余下的数字。你的任务就是找出这些关键数字,并按从大到小的顺序输出它们。
当做这道题就可以使用构建两个ArrayDeque来分别存值,对比,输出不同值