隊列-Java實現隊列數據結構

1、隊列的基本概念

隊列(queue)是一種特殊的線性表,特殊之處在於它只允許在表的前端(front)進行刪除操作,
而在表的後端(rear)進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。進行插入操作的端
稱爲隊尾,進行刪除操作的端稱爲隊頭。隊列中沒有元素時,稱爲空隊列。

隊列的數據元素又稱爲隊列元素。在隊列中插入一個隊列元素稱爲入隊,從隊列中刪除一個隊列元
素稱爲出隊。因爲隊列只允許在一端插入,在另一端刪除,所以只有最早進入隊列的元素才能最先從隊
列中刪除,故隊列又稱爲先進先出(FIFO—first in first out)線性表。

比如我們去電影院排隊買票,第一個進入排隊序列的都是第一個買到票離開隊列的人,而最後進入
排隊序列排隊的都是最後買到票的。
再比如在計算機操作系統中,有各種隊列在安靜的工作着,比如打印機在打印列隊中等待打印。
隊列分爲:
①、單向隊列(Queue):只能在一端插入數據,另一端刪除數據。
②、雙向隊列(Deque):每一端都可以進行插入數據和刪除數據操作。

這裏我們還會介紹一種隊列——優先級隊列,優先級隊列是比棧和隊列更專用的數據結構,在優先
級隊列中,數據項按照關鍵字進行排序,關鍵字最小(或者最大)的數據項往往在隊列的最前面,而數
據項在插入的時候都會插入到合適的位置以確保隊列的有序。

2、Java模擬單向隊列實現

在實現之前,我們先看下面幾個問題:
①、與棧不同的是,隊列中的數據不總是從數組的0下標開始的,移除一些隊頭front的數據後,隊
頭指針會指向一個較高的下標位置,如下圖:
在這裏插入圖片描述
②、我們再設計時,隊列中新增一個數據時,隊尾的指針rear 會向上移動,也就是向下標大的方
向。移除數據項時,隊頭指針 front 向上移動。那麼這樣設計好像和現實情況相反,比如排隊買電影
票,隊頭的買完票就離開了,然後隊伍整體向前移動。在計算機中也可以在隊列中刪除一個數之後,隊
列整體向前移動,但是這樣做效率很差。我們選擇的做法是移動隊頭和隊尾的指針。
③、如果向第②步這樣移動指針,相信隊尾指針很快就移動到數據的最末端了,這時候可能移除過
數據,那麼隊頭會有空着的位置,然後新來了一個數據項,由於隊尾不能再向上移動了,那該怎麼辦
呢?如下圖:
在這裏插入圖片描述
爲了避免隊列不滿卻不能插入新的數據,我們可以讓隊尾指針繞回到數組開始的位置,這也稱
爲“循環隊列”。
在這裏插入圖片描述
弄懂原理之後,Java實現代碼如下:

public class MyQueue {
  private Object[] queArray;
  //隊列總大小
  private int maxSize;
  //前端
  private int front;
  //後端
  private int rear;
  //隊列中元素的實際數目
  private int nItems;
 
  public MyQueue(int s){
    maxSize = s;
    queArray = new Object[maxSize];
    front = 0;
    rear = -1;
    nItems = 0;
 }
 
  //隊列中新增數據
  public void insert(int value){
    if(isFull()){
      System.out.println("隊列已滿!!!");
   }else{
      //如果隊列尾部指向頂了,那麼循環回來,執行隊列的第一個元素
      if(rear == maxSize -1){
        rear = -1;
     }
      //隊尾指針加1,然後在隊尾指針處插入新的數據
      queArray[++rear] = value;
      nItems++;
   }
 }
 
  //移除數據
  public Object remove(){
    Object removeValue = null ;
    if(!isEmpty()){
      removeValue = queArray[front];
      queArray[front] = null;
      front++;
      if(front == maxSize){
        front = 0;
     }
      nItems--;
      return removeValue;
   }
    return removeValue;
 }
 
  //查看對頭數據
  public Object peekFront(){
    return queArray[front];
 }

  //判斷隊列是否滿了
  public boolean isFull(){
    return (nItems == maxSize);
 }
 
  //判斷隊列是否爲空
  public boolean isEmpty(){
    return (nItems ==0);
 }
 
  //返回隊列的大小
  public int getSize(){
    return nItems;
 }
 
}

測試

package com.qianfeng.test;
import com.qianfeng.datastructure.MyQueue;
public class MyQueueTest {
  public static void main(String[] args) {
    MyQueue queue = new MyQueue(3);
    queue.insert(1);
    queue.insert(2);
    queue.insert(3);//queArray數組數據爲[1,2,3]
   
    System.out.println(queue.peekFront()); //1
    queue.remove();//queArray數組數據爲[null,2,3]
    System.out.println(queue.peekFront()); //2
   
    queue.insert(4);//queArray數組數據爲[4,2,3]
    queue.insert(5);//隊列已滿,queArray數組數據爲[4,2,3]
 }
}

3、雙端隊列

雙端隊列就是一個兩端都是結尾或者開頭的隊列, 隊列的每一端都可以進行插入數據項和移除數據
項,這些方法可以叫做:
insertRight() 、 insertLeft() 、 removeLeft() 、 removeRight()

如果嚴格禁止調用insertLe?()和removeLe?()(或禁用右端操作),那麼雙端隊列的功能就和前面講
的棧功能一樣。

如果嚴格禁止調用insertLe?()和removeRight(或相反的另一對方法),那麼雙端隊列的功能就和單向
隊列一樣了。

4、優先級隊列

優先級隊列(priority queue)是比棧和隊列更專用的數據結構,在優先級隊列中,數據項按照關鍵
字進行排序,關鍵字最小(或者最大)的數據項往往在隊列的最前面,而數據項在插入的時候都會插入
到合適的位置以確保隊列的有序。

優先級隊列 是0個或多個元素的集合,每個元素都有一個優先權,對優先級隊列執行的操作有:
(1)查找
(2)插入一個新元素
(3)刪除
一般情況下,查找操作用來搜索優先權最大的元素,刪除操作用來刪除該元素 。對於優先權相同的
元素,可按先進先出次序處理或按任意優先權進行。

這裏我們用數組實現優先級隊列,這種方法插入比較慢,但是它比較簡單,適用於數據量比較小並
且不是特別注重插入速度的情況。
後面我們會講解堆,用堆的數據結構來實現優先級隊列,可以相當快的插入數據。

數組實現優先級隊列,聲明爲int類型的數組,關鍵字是數組裏面的元素,在插入的時候按照從大到
小的順序排列,也就是越小的元素優先級越高。

public class PriorityQue {
  private int maxSize;
  private int[] priQueArray;
  private int nItems;
 
  public PriorityQue(int s){
    maxSize = s;
    priQueArray = new int[maxSize];
    nItems = 0;
 }
 
  //插入數據
  public void insert(int value){
    int j;
    if(nItems == 0){
      priQueArray[nItems++] = value;
   }else{
      j = nItems -1;
      //選擇的排序方法是插入排序,按照從大到小的順序排列,越小的越在隊列的頂端
      while(j >=0 && value > priQueArray[j]){
        priQueArray[j+1] = priQueArray[j];
        j--;
     }
      priQueArray[j+1] = value;
      nItems++;
   }
 }
 
  //移除數據,由於是按照大小排序的,所以移除數據我們指針向下移動
  //被移除的地方由於是int類型的,不能設置爲null,這裏的做法是設置爲 -1
  public int remove(){
    int k = nItems -1;
    int value = priQueArray[k];
    priQueArray[k] = -1;//-1表示這個位置的數據被移除了
    nItems--;
    return value;
 }
 
  //查看優先級最高的元素
  public int peekMin(){
    return priQueArray[nItems-1];
 }
 
  //判斷是否爲空
  public boolean isEmpty(){
    return (nItems == 0);
 }
 
  //判斷是否滿了
  public boolean isFull(){
    return (nItems == maxSize);
 }
}

insert() 方法,先檢查隊列中是否有數據項,如果沒有,則直接插入到下標爲0的單元裏,否則,從
數組頂部開始比較,找到比插入值小的位置進行插入,並把 nItems 加1.

remove 方法直接獲取頂部元素。
優先級隊列的插入操作需要 O(N)的時間,而刪除操作則需要O(1) 的時間,後面會講解如何通過 堆
來改進插入時間。

5、總結

隊列的三種形式,分別是單向隊列、雙向隊列以及優先級隊列。其實大家聽名字也可以聽得出來他們
之間的區別,單向隊列遵循先進先出的原則,而且一端只能插入,另一端只能刪除。雙向隊列則兩端都
可插入和刪除,如果限制雙向隊列的某一段的方法,則可以達到和單向隊列同樣的功能。最後優先級隊
列,則是在插入元素的時候進行了優先級別排序,在實際應用中單項隊列和優先級隊列使用的比較多。

稍微總結一下:
①、棧、隊列(單向隊列)、優先級隊列通常是用來簡化某些程序操作的數據結構,而不是主要作
爲存儲數據的。
②、在這些數據結構中,只有一個數據項可以被訪問。
③、棧允許在棧頂壓入(插入)數據,在棧頂彈出(移除)數據,但是隻能訪問最後一個插入的數
據項,也就是棧頂元素。
④、隊列(單向隊列)只能在隊尾插入數據,對頭刪除數據,並且只能訪問對頭的數據。而且隊列
還可以實現循環隊列,它基於數組,數組下標可以從數組末端繞回到數組的開始位置。
⑤、優先級隊列是有序的插入數據,並且只能訪問當前元素中優先級別最大(或最小)的元素。
⑥、這些數據結構都能由數組實現,但是可以用別的機制(後面講的鏈表、堆等數據結構)實現。

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