第二十四篇 玩轉數據結構——隊列(Queue)
阿新 • • 發佈:2018-07-07
stat 基礎 ann move 打印 圖片 data image 線性
1.. 隊列基礎
- 隊列也是一種線性結構;
- 相比數組,隊列所對應的操作數是隊列的子集;
- 隊列只允許從一端(隊尾)添加元素,從另一端(隊首)取出元素;
- 隊列的形象化描述如下圖:
-
- 隊列是一種先進先出(First In First Out)的數據結構;
- 任務目標如下:
-
Queue<E> ·void enqueue(E) //入隊 ·E dequeue() //出隊 ·E getFront() //查看隊首元素 ·int getSize() //查看隊列中元素的個數 ·boolean isEmpty() //
- 需要提一下,從用戶的角度來看,只要實現上述操作就好,具體底層實現,用戶並不關心,實際上,底層確實有多種實現方式。
- 我們準備在之前實現的動態數組基礎上,來實現"隊列"這種數據結構。
- 先定義一個接口Interface,如下:
-
public interface Queue<E> { int getSize(); boolean isEmpty(); void enqueue(E e); E dequeue(); E getFront(); }
- 實現基於Array類的ArrayQueue類,並進行測試:
-
public class ArrayQueue<E> implements Queue<E> { private Array<E> array; //構造函數 public ArrayQueue(int capacity) { array = new Array<>(capacity); } //無參數構造函數 public ArrayQueue() { array = new Array<>(); } //實現getSize()方法 @Override
- 輸出結果:
-
Queue: front [0, 1, 2, 3, 4] tail Queue: front [1, 2, 3, 4] tail
3.. 數組隊列的時間復雜度分析:
-
ArrayQueue<E> ·void enqueue(E) O(1) 均攤 ·E dequeue() O(n) ·E getFront() O(1) ·int getSize() O(1) ·boolean isEmpty() O(1)
- 數組隊列的出隊操作的復雜度是O(n),性能很差,解決方法就是使用循環隊列(Loop Queue)
- 循環隊列的示意圖如下:
-
- 實現循環隊列的業務邏輯,並進行測試:
-
public class LoopQueue<E> implements Queue<E> { private E[] data; private int front, tail; private int size; //構造函數 public LoopQueue(int capacity) { data = (E[]) new Object[capacity + 1]; front = 0; tail = 0; size = 0; } //無參數構造函數 public LoopQueue() { this(10); //直接調用有參數的構造函數,然後傳入一個默認值 } //實現getCapacity方法 public int getCapacity() { return data.length - 1; } //實現isEmpty方法 @Override public boolean isEmpty() { return front == tail; } //實現getSize方法 @Override public int getSize() { return size; } //實現enqueue方法 @Override public void enqueue(E e) { //判斷隊列是否已滿 if ((tail + 1) % data.length == front) { resize(getCapacity() * 2); } data[tail] = e; tail = (tail + 1) % data.length; size++; } //實現dequeue方法 @Override public E dequeue() { //判斷隊列是否為空 if (isEmpty()) { throw new IllegalArgumentException("Cannot dequeue from an empty queue."); } E ret = data[front]; data[front] = null; front = (front + 1) % data.length; size--; if (size == getCapacity() / 4 && getCapacity() / 2 != 0) { resize(getCapacity() / 2); } return ret; } //實現getFront方法 @Override public E getFront() { if (isEmpty()) { throw new IllegalArgumentException("Queue is empty."); } return data[front]; } //實現resize方法 private void resize(int newCapacity) { E[] newData = (E[]) new Object[newCapacity + 1]; for (int i = 0; i < size; i++) { newData[i] = data[(i + front) % data.length]; } data = newData; front = 0; tail = size; } //方便打印測試 @Override public String toString() { StringBuilder res = new StringBuilder(); res.append(String.format("Queue: size=%d, capacity=%d\n", size, getCapacity())); res.append("front ["); for (int i = front; i != tail; i = (i + 1) % data.length) { res.append(data[i]); if ((i + 1) % data.length != tail) { res.append(", "); } } res.append("] tail"); return res.toString(); } //測試 public static void main(String[] args) { LoopQueue<Integer> queue = new LoopQueue<>(); // 測試入隊 for (int i = 0; i < 5; i++) { queue.enqueue(i); } System.out.println(queue); // 測試出隊 queue.dequeue(); System.out.println(queue); } }
- 輸出結果:
-
Queue: size=5, capacity=10 front [0, 1, 2, 3, 4] tail Queue: size=4, capacity=10 front [1, 2, 3, 4] tail
5.. 循環隊列的復雜度分析
-
LoopQueue<E> ·void enqueue(E) O(1) 均攤 ·E dequeue() O(1) 均攤 ·E getFront() O(1) ·int getSize() O(1) ·boolean isEmpty() O(1)
6.. 使用簡單算例測試ArrayQueue與LoopQueue的性能差異
-
import java.util.Random; public class Main { // 測試使用q運行opCount個enqueue和dequeue操作所需要的時間,單位:秒 private static double testQueue(Queue<Integer> q, int opCount) { long startTime = System.nanoTime(); Random random = new Random(); for (int i = 0; i < opCount; i++) { q.enqueue(random.nextInt(Integer.MAX_VALUE)); } for (int i = 0; i < opCount; i++) { q.dequeue(); } long endTime = System.nanoTime(); return (endTime - startTime) / 1000000000.0; } public static void main(String[] args) { int opCount = 100000; ArrayQueue<Integer> arrayQueue = new ArrayQueue<>(); double time1 = testQueue(arrayQueue, opCount); System.out.println("ArrayQueue, time: " + time1 + " s"); LoopQueue<Integer> loopQueue = new LoopQueue<>(); double time2 = testQueue(loopQueue, opCount); System.out.println("LoopQueue, time: " + time2 + " s"); } }
- 輸出結果
-
ArrayQueue, time: 2.88077896 s LoopQueue, time: 0.01140229 s
第二十四篇 玩轉數據結構——隊列(Queue)