1. 使用數組實現一個簡單的隊列
/** * =========================== * 隊列首部 00000000000000000000000000 隊列尾部 * =========================== */ public class ArrayQueue<Element> implements Queue<Element>{ // 通過內部的array來實現 private Array<Element> array; // 構造函數 public ArrayQueue(int capacity){ this.array = new Array<>(capacity); } // 默認的構造函數 public ArrayQueue(){ this.array = new Array<>(); } @Override public int getSize() { return this.array.getSize(); } @Override public boolean isEmpty() { return this.array.isEmpty(); } public int getCapacity(){ return this.array.getCapacity(); } @Override public void enqueue(Element element) { // 進入隊列(數組的末尾來添加元素) this.array.addLast(element); } @Override public Element dequeue() { // 出隊列(刪除最后一個元素),數組的第一個元素 return this.array.removeFirst(); } @Override public Element getFront() { // 獲取第一個元素(對首部的第一個元素) return this.array.getFirst(); } @Override public String toString(){ StringBuilder stringBuilder = new StringBuilder(); // 使用自定義的方式實現數組的輸出 stringBuilder.append("Queue:"); // 開始實現數組元素的查詢 stringBuilder.append("front ["); for (int i = 0; i < this.array.getSize(); i++) { stringBuilder.append(this.array.get(i)); // 開始實現數組元素的回顯(只要下表不是最后一個元素的話,就直接輸出這個元素) if (i != this.array.getSize()-1) stringBuilder.append(", "); } stringBuilder.append("] tail"); // 實現數組元素的輸出 return stringBuilder.toString(); } }
2. 使用數組實現一個循環隊列(維護一個size變量)
/** * 循環隊列的幾個要點: * 1. tail = head 說明隊列就是滿的 * 2. 循環隊列需要空出來一個位置 */ public class LoopQueue<E> implements Queue { private E[] data; private int head; // 首部指針 private int tail; // 尾部指針 private int size; // 可以通過head以及tail的位置情況去計算size的大小【注意是不能直接使用getCapacity的】 // 實現循環隊列 public LoopQueue() { // 設置一個隊列的默認的大小 this(10); } // 循環隊列的實現 public LoopQueue(int capacity) { this.data = (E[]) new Object[capacity + 1]; // 需要多出來一個 this.head = 0; this.tail = 0; this.size = 0; } @Override public int getSize() { // 計算容量大小 return this.size; } // capacity表示的這個隊列中最大可以容納的元素個數【這是一個固定值】 @Override public int getCapacity() { // 由於隊列默認占了一個空的位置,因此sizt = this.length-1 return this.data.length - 1; } // 當head和tail的值相同的時候隊列滿了 public boolean isEmpty() { return head == tail; } @Override public void enqueue(Object value) { // 1. 開始判斷隊列有沒有充滿, 因為數組的下表是不會改變的,所以這里需要以數組的下標為一個參考點, 而不是this.data.length-14 if ((tail + 1) % this.data.length == head) { // 2. 開始進行擴容, 原來的二倍, 由於默認的時候已經白白浪費了一個空間,因此這里就直接擴容為原來的二倍即可 resize(2 * getCapacity()); } // 2. 如果沒有滿的話,就開始添加元素 this.data[tail] = (E) value; // 3. 添加完畢之后(tail是一個循環的位置,不可以直接相加) // this.tail = 2; this.tail = (this.tail+1) % data.length; // data.length對於數組的下標 // int i = (this.tail + 1) % data.length; // 4. 隊里的長度 this.size++; } /** * 開始resize數組元素 * * @param capacity */ private void resize(int capacity) { // 開始進行數組的擴容 // 1. 創建一個新的容器(默認多一個位置) E[] newData = (E[]) new Object[capacity + 1]; // 2. 開始轉移元素到新的容器 for (int i = 0; i < size; i++) { int index = (head + i) % data.length; newData[i] = data[index]; } // 添加完畢之后,開始回收之前的data,data里面的數據一直是最新的數據 this.data = newData; // jvm的垃圾回收機制會自動回收內存data // 3. 添加完畢 this.head = 0; // 4. 指向尾部 this.tail = size; } @Override public Object dequeue() { // 1. 先來看下隊列中有沒有元素數據 if (this.size == 0) throw new IllegalArgumentException("Empty queue cannot dequeue!"); // 2. 開始看一下內存的大小 Object ret = this.data[head]; // 3. 開始刪除數據 this.data[head] = null; // TODO 注意點:直接使用+1而不是使用++ // 4. 開始向后移動, 為了防止出錯,盡量使用 this.head+1來進行操作,而不是使用 this.haad++, 否則可能會出現死循環的情況【特別注意!!!!!!!!!!!!】 this.head = (this.head+1) % data.length; // 5. 計算新的容量大小 this.size--; // 6. 開始進行縮容操作, d當容量為1, size為0的時候,就不需要縮小容量、、 if (this.size == this.getCapacity() / 4) // 縮小為原來的一半大小的容量 resize(this.getCapacity() / 2); // 獲取出隊列的元素 return ret; } @Override public Object getFront() { // 由於front的位置一直是隊列的第一個元素,直接返回即可 if (this.size == 0) throw new IllegalArgumentException("Empty queue cannot get element!"); return this.data[head]; } @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(String.format("Array : size=%d, capacity=%d; ", size, getCapacity())); stringBuilder.append("LoopQueue head ["); // 第一個元素在head的位置,最后一個元素在tail-1的位置 // 注意循環隊列的遍歷方式,是一個循環 for (int i = this.head; i != tail; i = (i+1) % data.length) { stringBuilder.append(data[i]); // 只要不是最后一個元素的話, 由於循環條件中已經規定了i!=tail, 因此這里是不能直接這樣來判斷的 if ((i+1)%data.length == this.tail) { // 此時就是最后一個元素 } else { stringBuilder.append(", "); } } stringBuilder.append("] tail"); return stringBuilder.toString(); } }
3.使用數組實現一個循環隊列(不維護size變量)
/** * 使用數組實現一個循環隊列的數據結構 */ public class WithoutSizeLoopQueue<E> implements Queue<E> { private E[] data; private int head; private int tail; public WithoutSizeLoopQueue(int capacity) { // 泛型不能直接被實例化, 由於循環隊列默認會空出來一個位置,這里需要注意 this.data = (E[]) new Object[capacity + 1]; this.head = 0; this.tail = 0; } public WithoutSizeLoopQueue() { this(10); } @Override public int getSize() { // 計算數組的元素個數 if (tail >= head) { return tail - head; } else { int offSet = head - tail; return this.data.length - offSet; } } @Override public int getCapacity() { return data.length - 1; } @Override public void enqueue(E value) { // 開始進入隊列 // 1. 先來看下隊列有沒有滿 if ((tail + 1) % data.length == head) // 開始擴大容量 resize(2 * getCapacity()); // 2. 開始進入隊列 data[tail] = value; // 3. 開始移動下標 tail++; // 4. 開始更新容量【沒必要】 } public void resize(int newCapacity) { // 1. 更新為新的容量 E[] newData = (E[]) new Object[newCapacity + 1]; // 2. 開始轉移數據到新的數組中去 for (int i = 0; i < getSize(); i++) { newData[i] = data[(i + head) % data.length]; } // 3. 銷毀原來的數據信息 data = newData; // 【錯誤警告】bug:移動到新的這個數組里面之后,需要重新改變head和tail的位置【bug】 tail = getSize(); // 尾部節點始終指向最后一個元素的后面一個位置, 此時如果直接使用getSize實際上獲取到的是上一次的元素的個數,而不是最新的元素的個數信息(需要放在前面,否在獲取到的size就不是同一個了,特別重要) head = 0; // 頭節點指向的是第一個元素 // 4. 直接銷毀 newData = null; } @Override public E dequeue() { // 1.先來看下隊列是否為空 if (getSize() == 0) throw new IllegalArgumentException("Empty queue cannot dequeue!"); // 2.開始出head位置的元素 E value = data[head]; // 3. 刪除元素 data[head] = null; // 4. 移動head head = (head+1) % data.length; // 此時需要進行一次判斷 if (getSize() == getCapacity() / 4 && getCapacity() / 2 != 0) resize(getCapacity() / 2); // 如果數組縮小容量之后,這里的 return value; } @Override public E getFront() { return dequeue(); } @Override public String toString(){ // 開始遍歷輸出隊列的元素 StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(String.format("LoopQueue: size = %d, capacity = %d\n", getSize(), getCapacity())); stringBuilder.append("front "); // 從head位置開始遍歷 for (int i = head; i != tail ; i = (i+1) % data.length) { stringBuilder.append(data[i]); if ((i + 1) % data.length != tail) stringBuilder.append(", "); } return stringBuilder.append(" tail").toString(); } }
4. 使用鏈表實現一個隊列
/** * 使用鏈表實現的隊列 */ public class LinkedListQueue<E> implements Queue<E>{ // 這是一個內部類,只能在這個類的內部可以訪問(用戶不需要了解底層是如何實現的) private class Node { // 用於存儲元素 public E e; // 用於存儲下一個節點 public Node next; public Node(E e, Node next) { this.e = e; this.next = next; } public Node(E e) { this(e, null); } public Node() { this(null, null); } @Override public String toString() { return e.toString(); } } // 定義隊列需要的參數 private Node head, tail; private int size; public LinkedListQueue(){ this.head = null; this.tail = null; this.size = 0; } @Override public int getSize() { return this.size; } public boolean isEmpty(){ return this.size == 0; } @Override public int getCapacity() { return 0; } /** * 入隊 * @param value */ @Override public void enqueue(E value) { // 入隊從尾部開始 if (tail == null){ // 1. 如果此時沒有元素的話 tail = new Node(value); head = tail; } else { // 2. 如果已經存在了元素,那么隊列尾部肯定是不為空的,而是指向了一個空節點 tail.next = new Node(value); // 移動尾節點 tail = tail.next; } size++; } /** * 出隊 * @return */ @Override public E dequeue() { if (isEmpty()) throw new IllegalArgumentException("Empty queue cannot dequeue!"); // 1. 存儲出隊的元素 Node retNode = head; // 2.head節點下移動 head = head.next; // 3.刪除原來的頭節點 retNode.next = null; // 實際上刪除的是這個節點的數據域 // 如果刪除了最后一個元素之后 if (head == null) tail = null; size--; return retNode.e; } @Override public E getFront() { if (isEmpty()) throw new IllegalArgumentException("Empty queue cannot dequeue!"); return head.e; } @Override public String toString(){ StringBuilder stringBuilder = new StringBuilder(); Node cur = head; stringBuilder.append("head:"); // 1. 第一種循環遍歷的方式, 注意這里判斷的是每一次的cur是否為空, 而不是判斷cur.next, 否則第一個元素是不能打印輸出的 while (cur != null) { stringBuilder.append(cur + "->"); // 繼續向后移動節點 cur = cur.next; } stringBuilder.append("NULL tail"); return stringBuilder.toString(); } }
5. 使用一個帶有虛擬頭結點的鏈表實現一個隊列
/** * 帶有虛擬頭結點的鏈表實現的隊列 */ public class DummyHeadLinkedListQueue<E> implements Queue<E> { // 這是一個內部類,只能在這個類的內部可以訪問(用戶不需要了解底層是如何實現的) private class Node { // 用於存儲元素 public E e; // 用於存儲下一個節點 public Node next; public Node(E e, Node next) { this.e = e; this.next = next; } public Node(E e) { this(e, null); } public Node() { this(null, null); } @Override public String toString() { return e.toString(); } } // 定義一個虛擬頭結點 private Node dummyHead, tail; private int size; public DummyHeadLinkedListQueue(){ this.dummyHead = new Node(null, null); this.tail = null; this.size = 0; } @Override public int getSize() { return this.size; } public Boolean isEmpty(){ return this.size == 0; } @Override public int getCapacity() { throw new IllegalArgumentException("LinkedList cannot getCapacity!"); } @Override public void enqueue(E value) { // 開始入隊列(從隊列的尾部進行入隊列) // 1. 構造插入的節點 Node node = new Node(value); // 2. 尾部插入節點 //bug : 由於默認的時候 tail為null,此時如果直接訪問tail.next 是錯誤的 if (tail == null) { dummyHead.next = node; // 說明此時隊列為空的 tail = node; } else { tail.next = node; // 3. 尾部節點向后移動 tail = tail.next; } // 4. 隊列長度增加 size++; } @Override public E dequeue() { // 元素出隊列從鏈表的頭部出去 // 1. 獲取頭結點的位置 Node head = dummyHead.next; // 緩存出隊的元素 Node retNode = head; // 2. 移動節點 dummyHead.next = head.next; // 4. 更新容量 size--; head = null; if (isEmpty()) tail = null; // bug修復 return (E) retNode; } @Override public E getFront() { return null; } @Override public String toString(){ StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("Queue head:"); Node cur = dummyHead.next; while (cur != null){ stringBuilder.append(cur + "->"); cur = cur.next; } return stringBuilder.append("null tail").toString(); } }
以上就是創建隊列實現的5中方式,每種方式都有各自的特點,就做個簡單總結吧!
隊列的時間復雜度分析:
+ 隊列Queue[數組隊列]
void enqueue(E) O(1) 均攤復雜度
E dequeue() O(n) 移出隊列首部的元素,需要其他元素向前補齊
E getFront() O(1)
void dequeue() O(1)
Int getSize() O(1)
bool isEmpty() O(1)
+ 循環隊列
void enqueue(E) O(1) 均攤復雜度
E dequeue() O(1)
E getFront() O(1)
void dequeue() O(1)
Int getSize() O(1)
bool isEmpty() O(1)
以上就是對於隊列實現的幾種方式的總結了。