說在前面,本篇文章不適合小白,需要先了解循環隊列的數組實現。
一、避輕就重,抓住核心
ArrayDeque容器類可能大家平時用得很少,但是其代碼的設計和實現真比我們平常經常用的ArrayList,Stack(Vector)要深刻的多!ArrayDeque容器類的方法裏有許多,但是核心的方法其實就4個,下面我們會具體介紹,但是要稍有一些耐心,因爲ArrayDeque涉及到的知識點還蠻多的。
二、鋪墊
ArrayDeque和LinkedList是Deque的兩個通用實現,並且官方更推薦使用AarryDeque用作棧和隊列。因此,要講ArrayDeque,首先要講Deque接口。Deque的含義是“double ended queue”,即雙端隊列,它既可以當作棧使用,也可以當作隊列使用。
下表列出了Deque與Queue相對應的接口:
下表列出了Deque與Stack對應的接口:
上述提到的方法,其實核心的就是我框出來的那4個,其他方法要麼是調用要麼就是被調用,內在邏輯在addFirst()
,addLast()
,removeFirst
,removeLast
中。
爲什麼選用這四個作爲代表呢?因爲這四個方法名起得好,具體原因後面再說,先來說ArrayDeque如何使用上面的函數實現隊列的,到這兒,大家可以對上面應該有了個印象,下面我們重新開始一段故事。
三、核心–循環隊列的數組實現
ArrayDeque的核心如果用一句話概括,就是循環隊列的數組實現,只不過加了雙向增刪數據。
那麼,首先,我們得先知道,循環隊列是如何用數組實現的。
3.1 循環隊列
自己網上百度,一定要弄懂循環隊列的數組實現原理,這對理解ArrayDeque很重要!
下面這個是我基於數組實現的循環隊列。
/*
數組實現的雙端隊列:本質上是循環隊列
head指向隊首元素,tail指向隊尾元素的下一空位
*/
public class ArrayBasedCircularQueue {
private int head=0;
private int tail=0;
protected int[] array;
//循環隊列的大小要比實際的要大1
public ArrayBasedCircularQueue(int capacity){
array=new int[capacity+1];
}
//入隊
public void enQueue(int item){
//判斷是否隊滿
if((tail+1)%(array.length)==head){
System.out.println("The queue is Full!");
return;
}
array[tail]=item;
tail=(tail+1)%(array.length);
}
//出隊
public int deQueue(){
//判斷是否隊空
if(tail==head){
System.out.println("The queue is Empty!");
return -1;
}
int temp=head;
head=(head+1)%(array.length);
return array[temp];
}
//打印隊列所有元素
public void print(){
for (int i = head; i % array.length != tail; i = (i + 1) % array.length){
System.out.println(array[i]);
}
}
public static void main(String[] args) {
ArrayBasedCircularQueue queue=new ArrayBasedCircularQueue(3);
queue.enQueue(1);
queue.enQueue(2);
queue.enQueue(3);
//隊滿
queue.enQueue(4);
queue.print();
//出隊
System.out.println("出隊:");
queue.deQueue();
queue.print();
//入隊
System.out.println("入隊:");
queue.enQueue(4);
queue.print();
//出隊
queue.deQueue();
//入隊
System.out.println("入隊:");
queue.enQueue(5);
queue.print();
}
}
學習編程,敲代碼是唯一的學習方法。上述代碼,希望你能敲一遍。對於上面代碼,不知你沒有發現一個問題,這個問題就是“循環隊列的大小爲什麼要比實際的要大1。”,也就是說“爲什麼循環隊列總有一個位置是空着的。”,這個原因,我理解是由於“head指向隊首元素,tail指向隊尾元素的下一空位”,那麼爲什麼“tail要指向隊尾元素的下一空位”,而不是“指向隊尾元素呢?” ,這裏,tail要指向隊尾元素的下一空位
不是我故意這麼設計的,JAVA容器類也是這麼實現的,原因是什麼呢?大家感興趣,可以好好琢磨琢磨。
3.2 循環隊列的雙向增刪
上面,我們實現的循環隊列,如果把循環隊列看成是一個圓,我們只是實現了順時針的增刪元素,順時針的增刪元素,enQueue()
和deQueue()
方法,其實就對應着addLast()
和removeFirst()
(顧命思義一下,動動腦筋,不難想到),對於ArrayDeque這種雙端隊列,我們還需要實現逆時針的增刪元素,那就是addFirst()
和removeLast()
。
/*
數組實現的雙端隊列:本質上是循環隊列
head指向隊首元素,tail指向隊尾元素的下一位
*/
public class ArrayBasedDeque {
private int head=0;
private int tail=0;
protected int[] array;
//循環隊列的大小要比實際的要大1
public ArrayBasedDeque(int capacity){
array=new int[capacity+1];
}
//入隊:從前往後 -->
/*
這在ArrayBasedDeque裏稱之爲addLast();
*/
public void enQueue(int item){
//判斷是否隊滿
if((tail+1)%(array.length)==head){
System.out.println("The queue is Full!");
return;
}
array[tail]=item;
tail=(tail+1)%(array.length);
}
//入隊:從後往前 <--
public void AddFirst(int item){
if((tail+1)%(array.length)==head){
System.out.println("The queue is Full!");
return;
}
array[head=(head - 1) & (array.length - 1)]=item;
}
//出隊:先進先出
/*
這在ArrayBasedDeque裏稱之爲removeFirst();
*/
public int deQueue(){
//判斷是否隊空
if(tail==head){
System.out.println("The queue is Empty!");
return -1;
}
int temp=head;
head=(head+1)%(array.length);
return array[temp];
}
//出隊:先進先出
public int removeLast(){
if(tail==head){
System.out.println("The queue is Empty!");
return -1;
}
return array[tail=(tail-1)& (array.length - 1)];
}
//打印隊列所有元素
public void print(){
for (int i = head; i % array.length != tail; i = (i + 1) % array.length){
System.out.println(array[i]);
}
}
}
上述代碼,還是得你敲一遍。我留一個問題,看你是否掌握了,addLast()、removeFirst()實現的隊列,和AddFirst(),removeLast()實現的隊列在存儲元素時,他們在數組中的順序是什麼樣的?
所以,到這,如果你弄懂了循環隊列的數組實現,並且是如何支持雙向增刪數據,那麼你應該不難想出,怎麼用addFirst()
,addLast()
,removeFirst
,removeLast
這四個方法來實現出棧,這需要你自己動手。