1. 程式人生 > 程式設計 >Java資料結構和算法系列———佇列

Java資料結構和算法系列———佇列

目錄

  • 1、佇列的基本概念
  • 2、Java模擬單向佇列實現
  • 3、雙端佇列
  • 4、優先順序佇列
  • 5、總結

1、佇列的基本概念

佇列(queue)是一種特殊的線性表,特殊之處在於它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作,和棧一樣,佇列是一種操作受限制的線性表。進行插入操作的端稱為隊尾,進行刪除操作的端稱為隊頭。佇列中沒有元素時,稱為空佇列。 佇列的資料元素又稱為佇列元素。在佇列中插入一個佇列元素稱為入隊,從佇列中刪除一個佇列元素稱為出隊。因為佇列只允許在一端插入,在另一端刪除,所以只有最早進入佇列的元素才能最先從佇列中刪除,故佇列又稱為先進先出(FIFO—first in first out)線性表。比如我們去電影院排隊買票,第一個進入排隊序列的都是第一個買到票離開佇列的人,而最後進入排隊序列排隊的都是最後買到票的。在比如在計算機作業系統中,有各種佇列在安靜的工作著,比如印表機在列印列隊中等待列印。

佇列分為:

①、單向佇列(Queue):只能在一端插入資料,另一端刪除資料。

②、雙向佇列(Deque):每一端都可以進行插入資料和刪除資料操作。

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

2、Java模擬單向佇列實現

在實現之前,我們先看下面幾個問題:

①、與棧不同的是,佇列中的資料不總是從陣列的0下標開始的,移除一些隊頭front的資料後,隊頭指標會指向一個較高的下標位置,如下圖:

②、我們再設計時,佇列中新增一個資料時,隊尾的指標rear 會向上移動,也就是向下標大的方向。移除資料項時,隊頭指標 front 向上移動。那麼這樣設計好像和現實情況相反,比如排隊買電影票,隊頭的買完票就離開了,然後隊伍整體向前移動。在計算機中也可以在佇列中刪除一個數之後,佇列整體向前移動,但是這樣做效率很差。我們選擇的做法是移動隊頭和隊尾的指標。

③、如果向第②步這樣移動指標,相信隊尾指標很快就移動到資料的最末端了,這時候可能移除過資料,那麼隊頭會有空著的位置,然後新來了一個資料項,由於隊尾不能再向上移動了,那該怎麼辦呢?如下圖:

為了避免佇列不滿卻不能插入新的資料,我們可以讓隊尾指標繞回到陣列開始的位置,這也稱為“迴圈佇列”。

弄懂原理之後,Java實現程式碼如下:

package com.ys.datastructure;
 
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.ys.test;
 
import com.ys.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,3]
        System.out.println(queue.peekFront()); //2
         
        queue.insert(4);//queArray陣列資料為[4,3]
        queue.insert(5);//佇列已滿,queArray陣列資料為[4,3]
    }
}
複製程式碼

3、雙端佇列

雙端佇列就是一個兩端都是結尾或者開頭的佇列, 佇列的每一端都可以進行插入資料項和移除資料項,這些方法可以叫做:

insertRight()、insertLeft()、removeLeft()、removeRight()

如果嚴格禁止呼叫insertLeft()和removeLeft()(或禁用右端操作),那麼雙端佇列的功能就和前面講的棧功能一樣。如果嚴格禁止呼叫insertLeft()和removeRight(或相反的另一對方法),那麼雙端佇列的功能就和單向佇列一樣了。

4、優先順序佇列

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

優先順序佇列 是0個或多個元素的集合,每個元素都有一個優先權,對優先順序佇列執行的操作有:

**(1)**查詢 **(2)**插入一個新元素 **(3)**刪除

一般情況下,查詢操作用來搜尋優先權最大的元素,刪除操作用來刪除該元素 。對於優先權相同的元素,可按先進先出次序處理或按任意優先權進行。這裡我們用陣列實現優先順序佇列,這種方法插入比較慢,但是它比較簡單,適用於資料量比較小並且不是特別注重插入速度的情況。後面我們會講解堆,用堆的資料結構來實現優先順序佇列,可以相當快的插入資料。陣列實現優先順序佇列,宣告為int型別的陣列,關鍵字是陣列裡面的元素,在插入的時候按照從大到小的順序排列,也就是越小的元素優先順序越高。

package com.ys.datastructure;
 
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、總結

本篇文章我們介紹了佇列的三種形式,分別是單向佇列、雙向佇列以及優先順序佇列。其實大家聽名字也可以聽得出來他們之間的區別,單向佇列遵循先進先出的原則,而且一端只能插入,另一端只能刪除。雙向佇列則兩端都可插入和刪除,如果限制雙向佇列的某一段的方法,則可以達到和單向佇列同樣的功能。最後優先順序佇列,則是在插入元素的時候進行了優先順序別排序,在實際應用中單項佇列和優先順序佇列使用的比較多。後面講解了堆這種資料結構,我們會用堆來實現優先順序佇列,改善優先順序佇列插入元素的時間。

通過前面講的棧以及本篇講的佇列這兩種資料結構,我們稍微總結一下:

①、棧、佇列(單向佇列)、優先順序佇列通常是用來簡化某些程式操作的資料結構,而不是主要作為儲存資料的。

②、在這些資料結構中,只有一個資料項可以被訪問。

③、棧允許在棧頂壓入(插入)資料,在棧頂彈出(移除)資料,但是隻能訪問最後一個插入的資料項,也就是棧頂元素。

④、佇列(單向佇列)只能在隊尾插入資料,對頭刪除資料,並且只能訪問對頭的資料。而且佇列還可以實現迴圈佇列,它基於陣列,陣列下標可以從陣列末端繞回到陣列的開始位置。

⑤、優先順序佇列是有序的插入資料,並且只能訪問當前元素中優先順序別最大(或最小)的元素。

⑥、這些資料結構都能由陣列實現,但是可以用別的機制(後面講的連結串列、堆等資料結構)實現。