1. 程式人生 > >第二十四篇 玩轉數據結構——隊列(Queue)

第二十四篇 玩轉數據結構——隊列(Queue)

stat 基礎 ann move 打印 圖片 data image 線性

1.. 隊列基礎
  • 隊列也是一種線性結構;
  • 相比數組,隊列所對應的操作數是隊列的子集;
  • 隊列只允許從一端(隊尾)添加元素,從另一端(隊首)取出元素;
  • 隊列的形象化描述如下圖:
  • 技術分享圖片

  • 隊列是一種先進先出(First In First Out)的數據結構;
2.. 隊列的實現
  • 任務目標如下:
  • 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
        
    public int getSize() { return array.getSize(); } //實現isEmpty方法 @Override public boolean isEmpty() { return array.isEmpty(); } //實現getCapacity方法 public int getCapacity() { return array.getCapacity(); } //實現enqueue方法 @Override public void enqueue(E e) { array.addLast(e); } //實現dequeue方法 @Override public E dequeue() { return array.removeFirst(); } //實現getFront方法 @Override public E getFront() { return array.getFirst(); } //方便打印測試 @Override public String toString() { StringBuilder res = new StringBuilder(); res.append("Queue: "); res.append("front ["); for (int i = 0; i < array.getSize(); i++) { res.append(array.get(i)); if (i != array.getSize() - 1) { res.append(", "); } } res.append("] tail"); return res.toString(); } // 測試 public static void main(String[] args){ ArrayQueue<Integer> queue = new ArrayQueue<>(); // 測試入隊 for(int i=0;i<5;i++){ queue.enqueue(i); } System.out.println(queue); // 測試出隊 queue.dequeue(); System.out.println(queue); } }

  • 輸出結果:
  • 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)

4.. 循環隊列
  • 數組隊列的出隊操作的復雜度是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)