【小白學演算法】3. 迴圈佇列

把蘋果v咬哭發表於2021-03-13

上一章中,使用了陣列模擬了佇列。但是留下的問題是,把資料取完後,再往裡加資料就不行了。

一、假溢位

這是因為陣列的末尾已經被佔用了,入隊會繼續在陣列後面增加,於是產生陣列越界。
但是實際上,陣列裡是有空閒位置的,這種也可以叫“假溢位”。

為了解決“假溢位”的問題,於是乎有了迴圈佇列。

既然陣列後面滿了,頭部有空,那繼續加進來的元素從頭開始放即可。

接著上圖,這時候有a6入隊,於是rear的下標指向a6的下一個元素位置,看起來沒什麼問題。

但是繼續有新的元素a7入隊的時候,因為front一直沒變,這時候rear指標跟front就重合了,也就是說此時front=rear

可是在上一章的程式碼裡,我們是用front=rear來判斷是否為空陣列的。

    // 判斷佇列是否為空
    public boolean isEmpty() {
        return rear == front;
    }

現在是相等了,條件滿足,但是陣列是滿的。

二、迴圈佇列判斷是空是滿

這種情況看起來確實比較辣手,那如果不讓這種情況出現不就可以了麼?

假設,我們讓陣列始終保留一個元素空間,也就是讓rear指向佇列中最後一個元素的後一個位置。
比如,當a6放入之後,此時就當做佇列已經滿了,這樣的話就不會出現上述的情況了。

為了實現這種思路,front也需要做調整。在上一章中,front初始位置是指在了隊頭元素的前一個,現在我們讓它就指在
第一個元素。佇列的最大尺寸還是maxSize,那麼,現在判斷佇列滿的條件就可以是這樣(rear+1)%maxSize = front

驗證一下,下面的2種佇列滿情況:

  • 左圖:佇列中,maxSize=5,front=0,rear=4,判斷(4+1)% 5=0=front,佇列已滿
  • 右圖:佇列中,maxSize=5,front=2,rear=1,判斷(1+1)% 5=2=front,佇列已滿

繼續驗證一下,佇列沒滿的情況:

  • 佇列中,maxSize=5,front=2,rear=0,判斷(0+1)% 5=1≠front,佇列未滿

三、迴圈佇列的長度計算

佇列的長度,也就是說佇列中實際存了多少個元素。
此時需要考慮rear與front之間的三種情況:

  1. rear=front
  2. rear>front
  3. rear<front

第一種自然都清楚,佇列為空。

第二種,rear>front,此時佇列的長度為:rear-front

第三種,rear<front,比較複雜些。
佇列長度分為2段:一段是maxSize-front,另一段則是:0+rear(結合右圖理解)。故此時佇列總長度為兩段相加:rear-front+maxSize

所以佇列長度的通用計算公式為:(rear-front+maxSize)% maxSize

四、程式碼實現

既然現在佇列是滿是空的判斷條件有了,佇列長度也能計算出來了,那麼程式碼就好改造了(基於上一章),變成迴圈佇列。

package circle;

import java.util.Scanner;

public class CircleArrayQueue {
    public static void main(String[] args) {
        System.out.println("-----測試迴圈佇列-----");
        // 建立一個佇列
        CircleArray circleArray = new CircleArray(4);
        char key = ' '; //接受使用者輸入
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;
        // 輸出一個選單
        while (loop) {
            System.out.println("s(show): 顯示佇列");
            System.out.println("e(exit): 退出程式");
            System.out.println("a(add): 新增資料到佇列");
            System.out.println("g(get): 從佇列取出資料");
            System.out.println("h(head): 顯示隊首的資料");
            key = scanner.next().charAt(0); // 接收一個字元
            switch (key) {
                case 's':
                    circleArray.showQueue();
                    break;
                case 'a':
                    System.out.println("請要新增的數");
                    int value = scanner.nextInt();
                    circleArray.addQueue(value);
                    break;
                case 'g':
                    try {
                        int res = circleArray.getQueue();
                        System.out.printf("取出的資料是:%d", res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        int headValue = circleArray.showHeadQueue();
                        System.out.printf("隊首資料是:%d", headValue);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e':
                    scanner.close();
                    loop = false;
                    break;
            }
        }
        System.out.println("退出程式");
    }
}
class CircleArray {
    //表示陣列最大容量
    private int maxSize;
    // 佇列頭,由之前調整為指向佇列第一個元素
    private int front;
    // 佇列尾,由之前調整為指向佇列最後一個元素的後一個位置,為了空出一個元素位置
    private int rear;
    // 用於存放資料的陣列
    private int[] arr;

    // 構造器
    public CircleArray(int arrMaxSize) {
        maxSize = arrMaxSize;
        arr = new int[maxSize];
        front = 0;
        rear = 0;
    }

    // 判斷佇列是否已經存滿
    public boolean isFull() {
        return (rear + 1) % maxSize == front;
    }

    // 判斷佇列是否為空
    public boolean isEmpty() {
        return rear == front;
    }

    // 新增資料到佇列
    public void addQueue(int num) {
        // 判斷佇列是否滿了
        if (isFull()) {
            System.out.println("佇列已滿,不可加入資料");
            return;
        }
        arr[rear] = num;
        // 將rear後移,要考慮取模
        rear = (rear + 1) % maxSize;
    }

    // 拿出佇列資料
    public int getQueue() {
        // 判斷佇列是否空
        if (isEmpty()) {
            // 丟擲異常
            throw new RuntimeException("佇列為空,不可取資料");
        }
        int tempValue = arr[front];
        front = (front + 1) % maxSize; //也要考慮取模,防止front不停的增加,導致越界
        return tempValue;
    }

    // 顯示佇列所有資料
    public void showQueue() {
        // 遍歷
        if (isEmpty()) {
            System.out.println("佇列為空");
            return;
        }
        // 從front開始遍歷,遍歷多少個實際存的元素即可
        for (int i = front; i < front + CircleQueueSize(); i++) {
            System.out.printf("arr[%d]=%d\n", i % maxSize, arr[i % maxSize]);
        }

    }

    // 求出當前佇列有效資料的個數
    public int CircleQueueSize() {
        return (rear - front + maxSize) % maxSize;
    }

    // 顯示隊裡的隊首資料
    public int showHeadQueue() {
        if (isEmpty()) {
            // 丟擲異常
            throw new RuntimeException("佇列為空,不可取資料");
        }
        return arr[front];
    }
}

重點的改動在於取模。

相關文章