# 佇列(Queue)與環形佇列 使用場景: 排隊買票 ![](https://i.imgur.com/7B5muRr.png) ## 特徵 1. 先進先出(FIFO) 2. 有序列表,可以用陣列或連結串列來實現。 3. Queue兩個指針:rear 佇列的尾部(含)、front 佇列的頭部(不含)。 4. 新增數據時,front不動rear動;刪除數據時,rear不動front動 => 由尾端加入數據,由頭部取出。 我們可以以排隊買票為例,將排隊的隊列視作Queue,先排隊的人先買票,後到的人排在後面等待買票,並且先買票的人買完票之後就離開隊伍;我們將第一個買票的人稱之為front,隊伍最後一個等待買票的人為rear。 當1號買完票離開換2號買票時,此時的front變為2號。 ![Queue 取出數據 示意圖](https://i.imgur.com/dVbLcln.png) 當又有下一個人(5號)要買票,此時rear從4號變到5號身上。 ![Queue 新增數據 示意圖](https://i.imgur.com/9Bwu19n.png) 因此我們可以得出,當新增數據時,front不動rear動;刪除數據時,rear不動front動。 ## 陣列模擬佇列 當我們將數據存入佇列時稱為addQueue,addQueue的處理須要有兩個步驟: 1. 將尾指針往後移:rear+1,當 `front == rear` 為空。 2. 若尾指針 rear < maxSize-1 則將數據存入rear所指的陣列元素中;若 `rear == maxSize-1` 為佇列已滿。 ![](https://i.imgur.com/MRhZ3UA.png) ### 代碼實現 ```java= class ArrayQueue { private int maxSize; private int front; private int rear; private int[] arr; //創建佇列的構造函數 public ArrayQueue(int arrMaxSize) { maxSize = arrMaxSize; arr = new int[maxSize]; front = -1; //指向佇列頭部,分析出front是指向佇列頭部的前一個位置 rear = -1; //指向佇列尾部,指向佇列尾部的數據(佇列最後一個數據) } //判斷佇列是否滿 public boolean isFull() { return rear == maxSize-1; } //判斷佇列是否為空 public boolean isEmpty() { return rear == front; } //添加數據到佇列 public void addQueue(int n) { //判斷佇列是否滿 if(isFull()){ System.out.println("佇列已滿,不能加入數據"); return; } rear++; //讓rear後移 arr[rear] = n; } //獲取佇列數據出佇列 public int getQueue() { //判斷佇列是否空 if(isEmpty()){ throw new RuntimeException("佇列為空,無值可取"); //throw本身造成代碼return 不須另外寫return } front++; //讓rear後移 return arr[front]; } //顯示佇列數據 public void showQueue() { if(isEmpty()){ System.out.println("佇列空的,沒有數據"); return; } for(int i=0;i<arr.length;i++){ System.out.printf("arr[%d]=%d\n", i, arr[i]); } } } ``` ```java= public class ArrayQueueDemo { public static void main(String[] args){ ArrayQueue queue = new ArrayQueue(5); char key = ' '; Scanner sc = new Scanner(System.in); boolean loop = true; while(loop){ System.out.println("s(show)顯示佇列 | e(exit)退出程序 | a(add)添加數據到佇列 | g(get)從佇列取出數據 | h(head)查看佇列頭的數據"); key = sc.next().charAt(0); switch(key){ case 's': queue.showQueue(); break; case 'e': sc.close(); loop = false; break; case 'a': System.out.println("請輸入要添加的數據:"); int value = sc.nextInt(); queue.addQueue(value); break; case 'g': try { int result = queue.getQueue(); System.out.printf("取出的數據是 %d\n",result); } catch (Exception e) { System.out.println(e.getMessage()); } break; case 'h': try { int result = queue.headQueue(); System.out.printf("佇列頭的數據是 %d\n",result); } catch (Exception e) { System.out.println(e.getMessage()); } break; } } System.out.println("程序已結束~"); } ``` 目前使用會遇到的困難為:**佇列使用一次就不能再用,當我將數據取出,前端明明還有空位但想新增數據卻被告知佇列已滿。** 因此需要優化,將之改為**環形佇列**。 ## 陣列模擬環形佇列 **思路分析** 1. front的涵義做一個調整: front就指向佇列的第一個元素,也就是說 `arr[front]` 就是佇列的第一個元素,front 的初始值 = 0 2. rear的涵義做一個調整: rear就指向佇列的最後一個元素的後一個位置,因為希望空出一個空間用於判斷環形佇列是否為空或滿,rear 的初始值 = 0 3. 當佇列滿時,條件是 `(rear+1) % maxSize = front` 4. 當佇列為空時,條件是 `rear == front` 5. 佇列中有效的數據個數為 `(rear + maxSize - front) % maxSize` 使用環形佇列後,數據可以一直刪減、新增直到沒有空餘的位置,形成一個環狀的結構,因此不會浪費多餘的空間。 --- **舉個簡單的例子來加深印象好了:** 當我今天宣告一個環形佇列 MaxSize 為4,那就代表我這個佇列有效數據最大是3,因為有一個儲存單元要預留下來判斷是否為空還是滿。 添加數據 10,20,30 ***arr[0] = 10 , arr[1] = 20 , arr[2] = 30*** 取出數據,則只剩下 ***arr[1] = 20 , arr[2] = 30*** 新增數據 40 ***arr[1] = 20 , arr[2] = 30 , arr[3] = 40*** 繼續取出 20 和 30 ***arr[3]=40*** 再去新增數據 50,60 ***arr[3] = 40 , arr[0] = 50 , arr[1] = 60*** 再把 40 取出,加入70 會變為 ***arr[0] = 50 , arr[1] = 60 , arr[2] = 70*** --- ## 小整理 ### 佇列 - front的初始值 = -1 , rear的初始值 = -1 - 判斷佇列為空 `rear == front` - 判斷佇列為滿 `rear = maxSize -1` ### 環形佇列 - front的初始值 = 0 , rear的初始值 = 0 - 需預留一個儲存單元來判斷佇列是否為空還是滿 - 判斷佇列為空 `rear == front` - 判斷佇列為滿 `(rear+1) % maxSize == front` - 判斷佇列的有效數據個數 `(rear + maxSzie - front) % maxSize` front = 0 rear = 5 maxSize = 7 (5+7-0) % 7 = 5 ,共有五個有效數據。 **為什麼不是6?** 因為rear會指向最後一個元素的後一個位置。