在正式進行循環隊列學習以前,咱們先來看看在順序隊列中刪除隊首元素出現的問題html
(1)設一個容量爲capacity=8,size=5(a,b,c,d,e)的數組,左側爲隊首、右側爲隊尾。java
(2)出隊一個元素後,需總體往前移動一位git
#出隊github
#2總體前移一位數組
關於該種操做方式咱們很容易得出時間複雜度爲O(n)。app
這時咱們就想可不能夠在出隊元素後,總體元素不往前移,而是在數組中記下隊首front是誰,同時隊尾tail指向在下一次元素入隊時的位置,這樣當再有出隊時只須要維護一下front的指向便可,而不需移動元素。就這樣咱們就有了循環隊列的狀況。ide
2.循環隊列原理
(1)初始,數組總體爲空時,隊首front、隊尾tail指向同一個位置(數組索引爲0的地方)也即front==tail 時隊列爲空函數
(2)當往數組中添加元素後,oop
(3)出隊一個元素,front指向新的位置學習
(4)入隊元素,tail疊加
(5)當tail不能再增長時,數組前面還有空餘,此時循環隊列就該出場了。
此時數組應該變爲這樣:
在往數組中添加一個元素:
這樣數組就已經滿了(tail+1==front 隊列滿),開始出發擴容操做。【capacity中,浪費一個空間】。
爲了tail能返回到數組的前面位置,將隊列滿的表達式變爲 【(tail+1)%c==front】這樣數組就能夠循環移動了。
3.循環隊列代碼實現
新建一個類LoopQueue並實現接口Queue。
#1:接口Queue代碼以下:
package Queue; public interface Queue<E> { //獲取隊列中元素個數 int getSize(); //隊列中元素是否爲空 boolean isEmpty(); //入隊列 void enqueue(E e); //出隊列 E dequeue(); //獲取隊首元素 E getFront(); }
#2:LoopQueue相關代碼:
1 package Queue; 2 3 //循環隊列 4 public class LoopQueue<E> implements Queue<E> { 5 private E[] data; 6 private int front, tail; 7 private int size;//隊列中元素個數 8 9 //構造函數,傳入隊列的容量capacity構造函數 10 public LoopQueue(int capacity) { 11 data = (E[]) new Object[capacity + 1];//浪費與一個空間 12 front = 0; 13 tail = 0; 14 size = 0; 15 } 16 17 //無參構造函數,默認隊列的容量capacity=10 18 public LoopQueue() { 19 this(10); 20 } 21 22 //真正容量 23 public int getCapacity() { 24 return data.length - 1; 25 } 26 27 //隊列是否爲空 28 @Override 29 public boolean isEmpty() { 30 return front == tail; 31 } 32 33 //隊列中元素個數 34 @Override 35 public int getSize() { 36 return size; 37 } 38 39 //入隊列操做 40 @Override 41 public void enqueue(E e) { 42 if ((tail + 1) % data.length == front) {//隊列已滿,須要擴容 43 resize(getCapacity() * 2); 44 } 45 data[tail] = e; 46 tail = (tail + 1) % data.length; 47 size++; 48 } 49 50 //出隊操做 51 53 @Override 54 public E dequeue() { 55 if (isEmpty()) { 56 throw new IllegalArgumentException("隊列爲空"); 57 } 58 59 E ret = data[front]; 60 data[front] = null;//手動釋放 61 front = (front + 1) % data.length; 62 size--; 63 if (size == getCapacity() / 4 && getCapacity() / 2 != 0) { 64 resize(getCapacity() / 2); 65 } 66 return ret; 67 } 68 69 //獲取隊首元素 70 @Override 71 public E getFront() { 72 if (isEmpty()) { 73 throw new IllegalArgumentException("隊列爲空"); 74 } 75 return data[front]; 76 } 77 78 //改變容量 79 private void resize(int newCapacity) { 80 E[] newData = (E[]) new Object[newCapacity + 1]; 81 for (int i = 0; i < size; i++) { 82 newData[i] = data[(front + i) % data.length];//循環數組防止越界 83 } 84 data = newData; 85 front = 0; 86 tail = size; 87 } 88 89 90 @Override 91 public String toString() { 92 StringBuilder res = new StringBuilder(); 93 res.append(String.format("Queue:size=%d, capacity=%d\n", size, getCapacity())); 94 res.append("front ["); 95 for (int i = front; i != tail; i = (i + 1) % data.length) { 96 res.append(data[i]); 97 if ((i + 1) % data.length != tail) { 98 res.append(","); 99 } 100 } 101 res.append("] tail"); 102 return res.toString(); 103 } 104 105 10121 122 }
在關於LoopQueue類中須要注意的:
(1)第11行中的+1是capacity須要浪費一個空間,故在實例化是多加1
data = (E[]) new Object[capacity + 1];//浪費與一個空間
(2)地24行真正的容量是data.length-1,這是因爲有一個空間是浪費的。
data.length - 1;
(3)關於入隊中第46行tail值的說明
爲了保證入隊是循環操做,tail值的變化規律爲
tail = (tail + 1) % data.length;
(4)關於82行的數據遷移操做,取餘操做是爲了防止循環數組時越界。
newData[i] = data[(front + i) % data.length];//循環數組防止越界
#3直接在LoopQueue中添加一個main函數進行測試,相關代碼以下:
//測試用例 public static void main(String[] args) { LoopQueue<Integer> queue = new LoopQueue<Integer>(); for (int i = 0; i < 10; i++) { queue.enqueue(i); System.out.println(queue); if(i%3==2){//每添加3個元素出隊列一個 queue.dequeue(); System.out.println(queue); } } }
結果爲:
4.循環隊列時間複雜度
到此咱們就實現了一個循環隊列操做,解決了在順序隊列中出隊時的時間複雜度爲O(n)的狀況,在循環隊列中出隊的時間複雜度爲O(1)。
源碼地址 https://github.com/FelixBin/dataStructure/blob/master/src/Queue/LoopQueue.java
原文出處:https://www.cnblogs.com/wfaceboss/p/10628345.html