(希望我所描述的,給你帶來收獲!)
隊列是先進先出的線性表,在具體應用中通常用鏈表或者數組來實現!隊列結構可以類比日常生活中"排隊買東西",在隊伍末端的人可以看成新插入的元素,把排隊買東西的整個過程看作是入隊出隊操作,那么總是排在最末尾的那個人最后買東西、最后一個交易完再“出隊”!先進先出也可以換一種說法叫——后進后出。都是一個道理。
我們使用數組來實現我們的隊列,因為有動態數組的基礎,我們實現的隊列不再是固定容量的——動態數組篇章的傳送門:動態數組的實現
第一步:創建Queue<E>接口,定義ArrayQueue的一般操作
1 public interface Queue<E> { 2 int getSize(); 3 void enqueue(E e); 4 E dequeue(); 5 int getCapacity(); 6 boolean isEmpty(); 7 }
主要的兩個操作是 enqueue(入隊)和dequeue(出隊),我們的標准是,以動態數組尾部為隊列尾~以動態數組首位Array[0]位置為隊列首,為了保證隊列結構的特性,我們不提供給用戶查看隊列中間元素的操作!
第二步:新建ArrayQueue<E>,實現Queue接口的行為
1 public class ArrayQueue<E> implements Queue<E> { 2 3 Array<E> array; 4 5 public ArrayQueue(int capacity) { 6 array = new Array<E>(capacity); 7 } 8 9 public ArrayQueue() { 10 this(10); 11 } 12 13 @Override 14 public int getSize() { 15 return array.getSize(); 16 } 17 18 @Override 19 public void enqueue(E e) { 20 array.addLast(e); 21 } 22 23 @Override 24 public E dequeue() { 25 return array.removeFirst(); 26 } 27 28 @Override 29 public int getCapacity() { 30 return array.capacity(); 31 } 32 33 @Override 34 public boolean isEmpty() { 35 return array.isEmpty(); 36 } 37 }
對於ArrayQueue的一些操作,出隊操作的時間復雜度總是O(n)的、其他操作的均攤均為O(1)級別(關於均攤復雜度和震盪復雜度會另起一篇介紹)。出於對出隊操作的復雜度考慮,我們不希望有如此高昂的時間代價,我們可以基於數組實現循環隊列來降低該操作成本!
實現循環隊列的第一步:<新建一個LoopQueue<E>實現Queue<E>接口>
1 private E[] data; //存儲數據的數組 2 private int front; //隊列頭 3 private int tail; //隊列尾元素的下一個位置 4 private int size; //記錄數據總長度 5 public LoopQueue(int capacity) { 6 data = (E[])new Object[capacity + 1]; 7 } 8 9 public LoopQueue() { 10 this(10); 11 }
分別聲明了front、tail、size三個變量分別用來記錄隊列首位置、隊列尾位置的下一個空位置和數據總長度
值得思考的是:
第一:tail的實際index值(數組下標值,往后統一使用index替換)是否總是大於front的實際index值?(要考慮循環)
第二:front == tail 應該作為判定隊列是否為空的標志,那隊列滿的標志,front和tail的關系如何?
第二步:
1 public class LoopQueue<E> implements Queue<E> { 2 private E[] data; //存儲數據的數組 3 private int front; //隊列頭 4 private int tail; //隊列尾 5 private int size; //記錄數組總長度 6 public LoopQueue(int capacity) { 7 data = (E[])new Object[capacity + 1]; 8 } 9 10 public LoopQueue() { 11 this(10); 12 } 13 14 15 @Override 16 public int getSize() { 17 return size; 18 } 19 20 @Override 21 public void enqueue(E e) { 22 if ((tail + 1)%data.length == front) 23 resize(getCapacity()*2); 24 data[tail] = e; 25 tail = (tail + 1) % data.length; 26 size++; 27 } 28 29 private void resize(int newCapacity) { 30 E[] newData = (E[])new Object[newCapacity + 1]; 31 for (int i = 0; i < size; i++) { 32 newData[i] = data[(front+i) % data.length]; 33 } 34 front = 0; 35 tail = size; 36 data = newData; 37 } 38 39 @Override 40 public E dequeue() { 41 if (isEmpty()) 42 throw new IllegalArgumentException("dequeue is failed,Queue is empty"); 43 44 E e = data[front]; 45 data[front] = null; 46 front = (front + 1) % data.length; 47 size --; 48 if ((size - 1) == getCapacity()/4 && getCapacity()/2 != 0) 49 resize(getCapacity()/2); 50 return e; 51 } 52 53 @Override 54 public int getCapacity() { 55 return data.length - 1; 56 } 57 58 @Override 59 public boolean isEmpty() { 60 return tail == front; 61 } 62 63 /** 64 * 用於測試的toString方法 65 * @return 66 */ 67 @Override 68 public String toString() { 69 StringBuilder str = new StringBuilder(); 70 str.append(String.format("Queue:size = %d, capacity = %d\n",size,getCapacity())); 71 str.append("front ["); 72 for (int i = front; i != tail ; i = (i + 1) % data.length) { 73 str.append(data[i]); 74 if ((i+1) % data.length != tail) 75 str.append(","); 76 } 77 str.append("] tail"); 78 return String.valueOf(str); 79 } 80 }
tail的實際index值(數組下標值,往后統一使用index替換)是否總是大於front的實際index值?(要考慮循環)
答:tail的index值不總是大於front的index值,因為隊列滿足循環的效果,當數組尾部已經無法承載容量時,如果(0,front)之間有足夠空間,依然可以回到front之前的空間去存儲數據。
front == tail 應該作為判定隊列是否為空的標志,那隊列滿的標志,front和tail的關系如何?
答:隊列滿的標志應該是(tail + 1)% data.length == front(取模運算),因為整個隊列是循環的,若data.length == 9,tail == 8,front == 1時,我們的下一次enqueue(入隊)操作會在tail位置上插入一個元素,插入元素之后tail的index應該更新為 0;(取模運算的魅力就在於此)。思考一下,插入一個新的元素之后tail的值為0,若此時我再插入一個新的元素,tail的值是否會更新為1(要注意了!front == 1)?答案是不會的,因為我們插入元素之前總該要(tail + 1)% data.length == front 判斷隊列是否滿!(建議畫圖觀察!言語描述難免不夠深刻)
總結來看:tail位置上總是存儲一個用戶不可見的無關元素,只有當enqueue時,才會使得tail位置的元素有意義,然而插入新元素之后,tail又會改變為 tail = (tail+1) % data.length; 整體的設計使得實際容量capacity的數組只能容納(capacity - 1)個元素,換句話說,需要浪費一個空間!
這也是為什么在初始化數組時將用戶傳進的capacity進行加1的操作