看動畫學演算法之:佇列queue

flydean發表於2021-10-27

簡介

佇列Queue是一個非常常見的資料結構,所謂佇列就是先進先出的序列結構。

想象一下我們日常的排隊買票,只能向隊尾插入資料,然後從隊頭取資料。在大型專案中常用的訊息中介軟體就是一個佇列的非常好的實現。

佇列的實現

一個佇列需要一個enQueue入佇列操作和一個DeQueue操作,當然還可以有一些輔助操作,比如isEmpty判斷佇列是否為空,isFull判斷佇列是否滿員等等。

為了實現在佇列頭和佇列尾進行方便的操作,我們需要儲存隊首和隊尾的標記。

先看一下動畫,直觀的感受一下佇列是怎麼入隊和出隊的。

先看入隊:

再看出隊:

可以看到入隊是從隊尾入,而出隊是從隊首出。

佇列的陣列實現

和棧一樣,佇列也有很多種實現方式,最基本的可以使用陣列或者連結串列來實現。

先考慮一下使用陣列來儲存資料的情況。

我們用head表示隊首的index,使用rear表示隊尾的index。

當隊尾不斷插入,隊首不斷取資料的情況下,很有可能出現下面的情況:

上面圖中,head的index已經是2了,rear已經到了陣列的最後面,再往陣列裡面插資料應該怎麼插入呢?

如果再往rear後面插入資料,head前面的兩個空間就浪費了。這時候需要我們使用迴圈陣列。

迴圈陣列怎麼實現呢?只需要把陣列的最後一個節點和陣列的最前面的一個節點連線即可。

有同學又要問了。陣列怎麼變成迴圈陣列呢?陣列又不能像連結串列那樣前後連線。

不急,我們先考慮一個餘數的概念,假如我們知道了陣列的capacity,當要想陣列插入資料的時候,我們還是照常的將rear+1,但是最後除以陣列的capacity, 隊尾變到了隊首,也就間接的實現了迴圈陣列。

看下java程式碼是怎麼實現的:

public class ArrayQueue {

    //儲存資料的陣列
    private int[] array;
    //head索引
    private int head;
    //real索引
    private int rear;
    //陣列容量
    private int capacity;

    public ArrayQueue (int capacity){
        this.capacity=capacity;
        this.head=-1;
        this.rear =-1;
        this.array= new int[capacity];
    }

    public boolean isEmpty(){
        return head == -1;
    }

    public boolean isFull(){
        return (rear +1)%capacity==head;
    }

    public int getQueueSize(){
        if(head == -1){
            return 0;
        }
        return (rear +1-head+capacity)%capacity;
    }

    //從尾部入佇列
    public void enQueue(int data){
        if(isFull()){
            System.out.println("Queue is full");
        }else{
            //從尾部插入
            rear = (rear +1)%capacity;
            array[rear]= data;
            //如果插入之前佇列為空,將head指向real
            if(head == -1 ){
                head = rear;
            }
        }
    }

    //從頭部取資料
    public int deQueue(){
        int data;
        if(isEmpty()){
            System.out.println("Queue is empty");
            return -1;
        }else{
            data= array[head];
            //如果只有一個元素,則重置head和real
            if(head == rear){
                head= -1;
                rear = -1;
            }else{
                head = (head+1)%capacity;
            }
            return data;
        }
    }
}

大家注意我們的enQueue和deQueue中使用的方法:

rear = (rear +1)%capacity
head = (head+1)%capacity

這兩個就是迴圈陣列的實現。

佇列的動態陣列實現

上面的實現其實有一個問題,陣列的大小是寫死的,不能夠動態擴容。我們再實現一個能夠動態擴容的動態陣列實現。

    //因為是迴圈陣列,這裡不能做簡單的陣列拷貝
    private void extendQueue(){
        int newCapacity= capacity*2;
        int[] newArray= new int[newCapacity];
        //先全部拷貝
        System.arraycopy(array,0,newArray,0,array.length);
        //如果real<head,表示已經進行迴圈了,需要將0-head之間的資料置空,並將資料拷貝到新陣列的相應位置
        if(rear< head){
            for(int i=0; i< head; i++){
                //重置0-head的資料
                newArray[i]= -1;
                //拷貝到新的位置
                newArray[i+capacity]=array[i];
            }
            //重置real的位置
            rear= rear+capacity;
            //重置capacity和array
            capacity=newCapacity;
            array=newArray;
        }
    }

需要注意的是,在進行陣列擴充套件的時候,我們不能簡單的進行拷貝,因為是迴圈陣列,可能出現rear在head後面的情況。這個時候我們需要對陣列進行特殊處理。

其他部分是和普通陣列實現基本一樣的。

佇列的連結串列實現

除了使用陣列,我們還可以使用連結串列來實現佇列,只需要在頭部刪除和尾部新增即可。

看下java程式碼實現:

public class LinkedListQueue {
    //head節點
    private Node headNode;
    //rear節點
    private Node rearNode;

    class Node {
        int data;
        Node next;
        //Node的建構函式
        Node(int d) {
            data = d;
        }
    }

    public boolean isEmpty(){
        return headNode==null;
    }

    public void enQueue(int data){
        Node newNode= new Node(data);
        //將rearNode的next指向新插入的節點
        if(rearNode !=null){
            rearNode.next=newNode;
        }
        rearNode=newNode;
        if(headNode == null){
            headNode=newNode;
        }
    }

    public int deQueue(){
        int data;
        if(isEmpty()){
            System.out.println("Queue is empty");
            return -1;
        }else{
            data=headNode.data;
            headNode=headNode.next;
        }
        return data;
    }
}

佇列的時間複雜度

上面的3種實現的enQueue和deQueue方法,基本上都可以立馬定位到要入佇列或者出佇列的位置,所以他們的時間複雜度是O(1)。

本文的程式碼地址:

learn-algorithm

本文已收錄於 http://www.flydean.com/12-algorithm-queue/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!

相關文章