隊列的定義
隊列的特點是節點的排隊次序和出隊次序按入隊時間先后確定,即先入隊者先出隊,后入隊者后出隊。即我們常說的FIFO(first in first out)先進先出。
順序隊列定義及相關操作
順序存儲結構存儲的隊列稱為順序隊列,內部使用一個一維數組存儲,用一個隊頭指針front指向隊列頭部節點(即使用int類型front來表示隊頭元素的下標),用一個隊尾指針rear,指向隊列尾部元素(int類型rear來表示隊尾節點的下標)。
初始化隊列時: front = rear = -1 (非必須,也可設置初始值為0,在實現方法時具體修改)
隊列滿時: rear = maxSize-1 (其中maxSize為初始化隊列時,設置的隊列最大元素個數)
隊列為空時: front = rear
下面使用java實現一個基於一維數組的順序隊列,代碼如下:
1 /** 2 * 定義一個queue 3 */ 4 class ArrayQueue{ 5 private int[] data ; //隊列中存放的數據 6 private int maxSize ; //隊列的大小 7 private int front ;//指向隊列頭部的指針 8 private int rear ; //指向隊列尾部的指針 9 10 public ArrayQueue(int maxSize){ 11 this.maxSize = maxSize; 12 data = new int[maxSize]; 13 front = -1; 14 rear = -1; 15 } 16 17 /** 18 * 判斷隊列是否已滿 19 * @return 20 */ 21 public boolean isFull(){ 22 return rear == maxSize -1 ; 23 } 24 25 /** 26 * 判斷隊列是否為空 27 * @return 28 */ 29 public boolean isEmpty(){ 30 return rear == front; 31 } 32 33 /** 34 * 添加數據到隊列 35 * @param n 36 */ 37 public void add(int n){ 38 if(isFull()){ 39 System.out.println("隊列已滿,不能添加"); 40 return; 41 } 42 data[++rear] = n; 43 } 44 45 /** 46 * 顯示頭部數據 47 * @return 48 */ 49 public void head(){ 50 if(isEmpty()){
throw new RuntimeException("隊列為空"); 53 } 54 System.out.println(data[front+1]); 55 } 56 57 /** 58 * 取出頭部數據 59 * @return 60 */ 61 public int pop(){ 62 if(isEmpty()){
64 throw new RuntimeException("隊列為空"); 65 } 66 int a = data[++front]; 67 data[front] = 0; 68 return a; 69 } 70 71 /** 72 * 打印全部數據 73 */ 74 public void print(){ 75 if(isEmpty()){ 76 System.out.println("隊列為空"); 77 return; 78 } 79 for(int i=0;i<data.length;i++){ 80 System.out.printf("array["+i+"]=%d\n",data[i]); 81 } 82 } 83 }
簡單描述順序隊列的入隊(add方法):
1 public static void main(String []args) { 2 //1.聲明一個可以存儲6個元素的順序隊列,默認值為0,front 和rear指針為-1 3 ArrayQueue queue = new ArrayQueue(6); 4 //2.向順序隊列中添加元素 5 queue.add(1); 6 queue.add(2); 7 queue.add(3); 8 queue.add(4); 9 queue.add(5); 10 queue.add(6); 11 //2.1打印當前隊列元素 12 queue.print(); 13 //3.將順序隊列中元素取出 14 queue.pop(); 15 queue.pop(); 16 queue.pop(); 17 queue.pop(); 18 queue.pop(); 19 queue.pop(); 20 //4.隊列中元素全部取出 21 }
在代碼中初始化了一個大小為6的順序隊列,下圖展示了第一步(即代碼ArrayQueue queue = new ArrayQueue(6))中隊列元素及指針情況
其中front和rear指向的虛線框實際並不存在,僅用來表示初始化時的默認狀態,因我們實現的隊列元素使用int[]存儲元素,所以初始值均為0(如用Object[]或范型則初始值為null)
執行queue.add(1)方法后隊列的狀態如下圖:
可以看到向隊列中添加元素后,rear指針向后移動一個位置指向第一個元素位置,后面繼續添加后面5個元素后隊列如下圖所示
接下來看下隊列的出隊情況
當第一次執行queue.pop()方法后,隊列元素如上圖所示,此時隊列剩下5個元素
當第六次執行queue.pop()方法后,隊列元素如下圖所示
此時隊列中元素已全部出隊,按正常邏輯應該可以添加元素到隊列中,但此時添加元素卻會報隊列已滿錯誤(rear=maxSize-1),當然即使前面元素未出隊也會報相同錯誤。這就是我們常說的“假溢出”問題。為解決這個問題,就引出了我們的環形隊列。
環形隊列
環形隊列,顧名思義即讓普通隊列首尾相連,形成一個環形。當rear指向尾元素后,當隊列有元素出隊時,可以繼續向隊列中添加元素。
這里我使用的是rear指針指向最后一個節點的后一個元素,即會占用一個位置用來表示隊列已滿。
初始化隊列時: front = rear = 0
隊列滿時: ( rear +1 ) % maxSize == front (其中maxSize為初始化隊列時,設置的隊列最大元素個數)
這里不能使用rear = maxSize-1作為判斷隊滿的條件,因使用環形隊列方式實現,當第一次隊滿時,rear = maxSize -1,執行出隊操作后原隊頭位置空出,此時繼續執行入隊操作,則rear向后移動一個位置,則rear = 0,而此時隊列也是已滿狀態。所以只要rear 向前移動一個位置就等於front時,就是隊滿的情況。
隊列為空時: front == rear
先看下具體代碼
1 public class CycleQueue { 2 3 /** 4 * 5 */ 6 private int maxSize ; 7 private int data[] ; 8 private int front ; 9 /** 10 * 這里rear指向最后一個數據的后面一個位置,即隊列中有一個為空占位 11 */ 12 private int rear ; 13 14 public CycleQueue(int maxSize){ 15 this.maxSize = maxSize; 16 data = new int[maxSize]; 17 front = 0; 18 rear = 0; 19 } 20 21 /** 22 * 判斷隊列是否已滿 23 * 因是循環隊列,所以rear值可能小於front,所以不能使用rear == maxSize -1來判斷 24 * @return 25 */ 26 public boolean isFull(){ 27 return (rear + 1) % maxSize == front; 28 } 29 30 public boolean isEmpty(){ 31 return rear == front; 32 } 33 34 public void add(int n){ 35 if(isFull()){ 36 System.out.println("隊列已滿,不能添加"); 37 return; 38 } 39 data[rear] = n; 40 rear = (rear + 1) % maxSize; 41 } 42 43 public void head(){ 44 if(isEmpty()){ 45 throw new RuntimeException("隊列為空"); 46 } 47 System.out.println("head="+data[front]); 48 } 49 50 public int pop(){ 51 if(isEmpty()){ 52 throw new RuntimeException("隊列為空"); 53 } 54 int value = data[front]; 55 front = (front + 1) % maxSize; 56 return value; 57 } 58 59 public void print(){ 60 if(isEmpty()){ 61 System.out.println("隊列為空"); 62 return; 63 } 64 for(int i= front; i<front+size(); i++){ 65 System.out.printf("array"+(i%maxSize)+"=%d",data[i%maxSize]); 66 } 67 } 68 69 /** 70 * 因是循環隊列,所以會出現rear<front情況,這里需要+maxSize 71 * @return 72 */ 73 public int size(){ 74 return (rear - front + maxSize)%maxSize; 75 } 76 }
下面再以圖解的方式講解一下環形隊列的入隊出隊以及隊滿情況。當執行初始化代碼后
1 CycleQueue queue = new CycleQueue(6);
此時front = rear = 0,隊列為空。當第一次執行queue.add(1)后,環形隊列元素如下圖所示
當依次執行queue.add(2);queue.add(3);queue.add(4);queue.add(5);后,達到(rear+1)%maxSize=front (即rear=5)條件,隊列已滿不能添加新元素。此時環形隊列元素情況如下圖
所以這種情況會浪費一個空間來作為判滿的條件。
下面執行出隊操作,當第一次執行出隊操作queue.pop()方法后,環形隊列元素情況如下圖所示
此時 (rear+1)%maxSize = 0 不等於 front=1 ,所以可以繼續向隊列中添加元素,也就不會出現假溢出的情況。當執行入隊(例queue.add(6))操作后,rear = (rear+1)%maxSize 即rear=0,以此來生成環形隊列。此時隊列元素情況如下圖所示
另外,再說明下環形隊列有效元素個數問題,如果不是環形隊列,則有效元素個數size = rear - front。而使用環形實現后,會出現rear<front的情況,所以這里使用(rear-front+maxSize)%maxSize的方式計算有效元素個數。(或者在內部定義一個size屬性,當元素入隊時size++,當出隊時size--)
因此在打印隊列中元素時,從front位置開始至 front+size位置結束來循環打印有效元素。
總結
如果不實用環形隊列方式實現隊列,則會出現“假溢出”情況(即隊列滿后,將全部元素出隊卻不能繼續添加元素的情況)。而環形隊列會在隊頭元素出隊后,將隊尾指針rear重新分配為0,以達到循環使用隊列空間的目的。