隊列(基於動態數組的兩種實現——LoopQueue、ArrayQueue)


(希望我所描述的,給你帶來收獲!)

隊列先進先出線性表,在具體應用中通常用鏈表或者數組來實現!隊列結構可以類比日常生活中"排隊買東西",在隊伍末端的人可以看成新插入的元素,把排隊買東西的整個過程看作是入隊出隊操作,那么總是排在最末尾的那個人最后買東西、最后一個交易完再“出隊”!先進先出也可以換一種說法叫——后進后出。都是一個道理。

 

我們使用數組來實現我們的隊列,因為有動態數組的基礎,我們實現的隊列不再是固定容量的——動態數組篇章的傳送門:動態數組的實現

 

第一步:創建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的操作


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM