循環與循環雙端隊列

循環隊列 是一種線性數據結構,其操作表現基於 FIFO(先進先出)原則並且隊尾被連接在隊首之後以形成一個循環。它也被稱爲 環形緩衝器

循環隊列的一個好處是我們可以利用這個隊列之前用過的空間。在一個普通隊列裏,一旦一個隊列滿了,我們就不能插入下一個元素,即使在隊列前面仍有空間。但是使用循環隊列,我們能使用這些空間去存儲新的值。

爲什麼使用循環隊列?

這裏我們先對 隊列 的簡單實現進行簡單展示,隊列應支持兩種操作:入隊出隊

class MyQueue {
    // store elements
    private List<Integer> data;         
    // a pointer to indicate the start position
    private int p_start;            
    public MyQueue() {
        data = new ArrayList<Integer>();
        p_start = 0;
    }
    /** Insert an element into the queue. Return true if the operation is successful. */
    public boolean enQueue(int x) {
        data.add(x);
        return true;
    };    
    /** Delete an element from the queue. Return true if the operation is successful. */
    public boolean deQueue() {
        if (isEmpty() == true) {
            return false;
        }
        p_start++;
        return true;
    }
    /** Get the front item from the queue. */
    public int Front() {
        return data.get(p_start);
    }
    /** Checks whether the queue is empty or not. */
    public boolean isEmpty() {
        return p_start >= data.size();
    }     
}

缺點

上面的實現很簡單,但在某些情況下效率很低。 隨着起始指針的移動,浪費了越來越多的空間。 當我們有空間限制時,這將是難以接受的。

讓我們考慮一種情況,即我們只能分配一個最大長度爲 5 的數組。當我們只添加少於 5 個元素時,我們的解決方案很有效。 例如,如果我們只調用入隊函數四次後還想要將元素 10 入隊,那麼我們可以成功。

但是我們不能接受更多的入隊請求,這是合理的,因爲現在隊列已經滿了。但是如果我們將一個元素出隊呢?

實際上,在這種情況下,我們應該能夠再接受一個元素。

循環隊列 的需求迫在眉睫。

設計思想

這裏直接複製了 liweiwei1419 精彩的 題解

1、定義循環變量 front 和 rear 。一直保持這個定義,到底是先賦值還是先移動指針就很容易想清楚了。

front:指向隊列頭部第 1 個有效數據的位置;
rear:指向隊列尾部(即最後 1 個有效數據)的下一個位置,即下一個從隊尾入隊元素的位置。

(說明:這個定義是依據“動態數組”的定義模仿而來。)

2、爲了避免“隊列爲空”和“隊列爲滿”的判別條件衝突,我們有意浪費了一個位置。

浪費一個位置是指:循環數組中任何時刻一定至少有一個位置不存放有效元素。

判別隊列爲空的條件是:front == rear;

判別隊列爲滿的條件是:(rear + 1) % capacity == front;。可以這樣理解,當 rear 循環到數組的前面,要從後面追上 front,還差一格的時候,判定隊列爲滿。

3、因爲有循環的出現,要特別注意處理數組下標可能越界的情況。指針後移的時候,索引 + 1,並且要注意取模。

例題

本文的寫作目的就是爲了 再次手寫一邊算法練手

622.設計循環隊列

  • 難度:Medium

參考上述設計思路,很容易編寫代碼,需要注意的是取模代碼的編寫相對容易出問題。

實現代碼:

class MyCircularQueue {

    private int front;
    private int tail;
    private int capacity;
    private int[] arr;

    public MyCircularQueue(int k) {
        this.capacity = k + 1;
        this.arr = new int[capacity];

        this.front = 0;
        this.tail = 0;
    }

    public boolean enQueue(int value) {
        if (isFull()) return false;

        arr[tail] = value;
        tail = (tail + 1) % capacity;
        return true;
    }

    public boolean deQueue() {
        if (isEmpty()) return false;

        front = (front + 1) % capacity;
        return true;
    }

    public int Front() {
        if (isEmpty()) return -1;
        return arr[front];
    }

    public int Rear() {
        if (isEmpty()) return -1;
        return arr[(tail - 1 + capacity) % capacity];
    }

    public boolean isEmpty() {
        return front == tail;
    }

    public boolean isFull() {
        return (tail + 1) % capacity == front;
    }
}

641. 設計循環雙端隊列

  • 難度:Medium

和上題基本相似,除了多幾個取模運算,幾乎沒什麼變化。

class MyCircularDeque {

    private int front;
    private int tail;
    private int capacity;
    private int[] arr;

    public MyCircularDeque(int k) {
        this.capacity = k + 1;
        this.arr = new int[capacity];

        this.front = 0;
        this.tail = 0;
    }

    public boolean insertFront(int value) {
        if (isFull()) return false;

        front = (front - 1 + capacity) % capacity;
        arr[front] = value;
        return true;
    }

    public boolean insertLast(int value) {
        if (isFull()) return false;

        arr[tail] = value;
        tail = (tail + 1) % capacity;
        return true;
    }

    public boolean deleteFront() {
        if (isEmpty()) return false;

        front = (front + 1) % capacity;
        return true;
    }

    public boolean deleteLast() {
        if (isEmpty()) return false;

        tail = (tail - 1 + capacity) % capacity;
        return true;
    }

    public int getFront() {
        if (isEmpty()) return -1;
        return arr[front];
    }

    public int getRear() {
        if (isEmpty()) return -1;
        return arr[(tail - 1 + capacity) % capacity];
    }

    public boolean isEmpty() {
        return front == tail;
    }

    public boolean isFull() {
        return (tail + 1) % capacity == front;
    }
}

參考 & 感謝

文章絕大部分內容節選自LeetCode

https://leetcode-cn.com/problems/design-circular-queue

https://leetcode-cn.com/problems/design-circular-deque

感謝 liweiwei1419 提供的精彩的 題解

關於我

Hello,我是 卻把清梅嗅 ,如果您覺得文章對您有價值,歡迎 ❤️,也歡迎關注我的 博客 或者 GitHub

如果您覺得文章還差了那麼點東西,也請通過關注督促我寫出更好的文章——萬一哪天我進步了呢?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章