Java數(shù)據(jù)結(jié)構(gòu)和算法(五)——隊(duì)列

前面一篇博客我們講解了并不像數(shù)組一樣完全作為存儲(chǔ)數(shù)據(jù)功能恍风,而是作為構(gòu)思算法的輔助工具的數(shù)據(jù)結(jié)構(gòu)——棧蹦狂,本篇博客我們介紹另外一個(gè)這樣的工具——隊(duì)列誓篱。棧是后進(jìn)先出,而隊(duì)列剛好相反凯楔,是先進(jìn)先出窜骄。

1、隊(duì)列的基本概念

隊(duì)列(queue)是一種特殊的線性表摆屯,特殊之處在于它只允許在表的前端(front)進(jìn)行刪除操作邻遏,而在表的后端(rear)進(jìn)行插入操作,和棧一樣虐骑,隊(duì)列是一種操作受限制的線性表准验。進(jìn)行插入操作的端稱為隊(duì)尾,進(jìn)行刪除操作的端稱為隊(duì)頭廷没。隊(duì)列中沒(méi)有元素時(shí)糊饱,稱為空隊(duì)列。

隊(duì)列的數(shù)據(jù)元素又稱為隊(duì)列元素颠黎。在隊(duì)列中插入一個(gè)隊(duì)列元素稱為入隊(duì)另锋,從隊(duì)列中刪除一個(gè)隊(duì)列元素稱為出隊(duì)。因?yàn)殛?duì)列只允許在一端插入狭归,在另一端刪除砰蠢,所以只有最早進(jìn)入隊(duì)列的元素才能最先從隊(duì)列中刪除,故隊(duì)列又稱為先進(jìn)先出(FIFO—first in first out)線性表唉铜。

比如我們?nèi)ル娪霸号抨?duì)買(mǎi)票,第一個(gè)進(jìn)入排隊(duì)序列的都是第一個(gè)買(mǎi)到票離開(kāi)隊(duì)列的人律杠,而最后進(jìn)入排隊(duì)序列排隊(duì)的都是最后買(mǎi)到票的潭流。

在比如在計(jì)算機(jī)操作系統(tǒng)中,有各種隊(duì)列在安靜的工作著柜去,比如打印機(jī)在打印列隊(duì)中等待打印灰嫉。

隊(duì)列分為:

①、單向隊(duì)列(Queue):只能在一端插入數(shù)據(jù)嗓奢,另一端刪除數(shù)據(jù)讼撒。

②、雙向隊(duì)列(Deque):每一端都可以進(jìn)行插入數(shù)據(jù)和刪除數(shù)據(jù)操作股耽。

這里我們還會(huì)介紹一種隊(duì)列——優(yōu)先級(jí)隊(duì)列根盒,優(yōu)先級(jí)隊(duì)列是比棧和隊(duì)列更專(zhuān)用的數(shù)據(jù)結(jié)構(gòu),在優(yōu)先級(jí)隊(duì)列中物蝙,數(shù)據(jù)項(xiàng)按照關(guān)鍵字進(jìn)行排序炎滞,關(guān)鍵字最小(或者最大)的數(shù)據(jù)項(xiàng)往往在隊(duì)列的最前面诬乞,而數(shù)據(jù)項(xiàng)在插入的時(shí)候都會(huì)插入到合適的位置以確保隊(duì)列的有序册赛。

2钠导、Java模擬單向隊(duì)列實(shí)現(xiàn)

在實(shí)現(xiàn)之前,我們先看下面幾個(gè)問(wèn)題:

①森瘪、與棧不同的是牡属,隊(duì)列中的數(shù)據(jù)不總是從數(shù)組的0下標(biāo)開(kāi)始的,移除一些隊(duì)頭front的數(shù)據(jù)后扼睬,隊(duì)頭指針會(huì)指向一個(gè)較高的下標(biāo)位置逮栅,如下圖:

image

②、我們?cè)僭O(shè)計(jì)時(shí)痰驱,隊(duì)列中新增一個(gè)數(shù)據(jù)時(shí)证芭,隊(duì)尾的指針rear 會(huì)向上移動(dòng),也就是向下標(biāo)大的方向担映。移除數(shù)據(jù)項(xiàng)時(shí)废士,隊(duì)頭指針 front 也會(huì)向下移動(dòng)。那么這樣設(shè)計(jì)好像和現(xiàn)實(shí)情況相反蝇完,比如排隊(duì)買(mǎi)電影票官硝,隊(duì)頭的買(mǎi)完票就離開(kāi)了,然后隊(duì)伍整體向前移動(dòng)短蜕。在計(jì)算機(jī)中也可以在隊(duì)列中刪除一個(gè)數(shù)之后氢架,隊(duì)列整體向前移動(dòng),但是這樣做效率很差朋魔。我們選擇的做法是移動(dòng)隊(duì)頭和隊(duì)尾的指針岖研。

③、如果向第②步這樣移動(dòng)指針警检,相信隊(duì)尾指針很快就移動(dòng)到數(shù)據(jù)的最末端了孙援,這時(shí)候可能移除過(guò)數(shù)據(jù),那么隊(duì)頭會(huì)有空著的位置扇雕,然后新來(lái)了一個(gè)數(shù)據(jù)項(xiàng)拓售,由于隊(duì)尾不能再向上移動(dòng)了,那該怎么辦呢镶奉?如下圖:

image

為了避免隊(duì)列不滿卻不能插入新的數(shù)據(jù)础淤,我們可以讓隊(duì)尾指針繞回到數(shù)組開(kāi)始的位置,這也稱為“循環(huán)隊(duì)列”哨苛。

image

弄懂原理之后鸽凶,Java實(shí)現(xiàn)代碼如下:

package com.ys.datastructure;

public class MyQueue {
    private Object[] queArray;
    //隊(duì)列總大小
    private int maxSize;
    //前端
    private int front;
    //后端
    private int rear;
    //隊(duì)列中元素的實(shí)際數(shù)目
    private int nItems;
    
    public MyQueue(int s){
        maxSize = s;
        queArray = new Object[maxSize];
        front = 0;
        rear = -1;
        nItems = 0;
    }
    
    //隊(duì)列中新增數(shù)據(jù)
    public void insert(int value){
        if(isFull()){
            System.out.println("隊(duì)列已滿!=ㄇ汀吱瘩!");
        }else{
            //如果隊(duì)列尾部指向頂了,那么循環(huán)回來(lái)迹缀,執(zhí)行隊(duì)列的第一個(gè)元素
            if(rear == maxSize -1){
                rear = -1;
            }
            //隊(duì)尾指針加1使碾,然后在隊(duì)尾指針處插入新的數(shù)據(jù)
            queArray[++rear] = value;
            nItems++;
        }
    }
    
    //移除數(shù)據(jù)
    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;
    }
    
    //查看對(duì)頭數(shù)據(jù)
    public Object peekFront(){
        return queArray[front];
    }
    
    
    //判斷隊(duì)列是否滿了
    public boolean isFull(){
        return (nItems == maxSize);
    }
    
    //判斷隊(duì)列是否為空
    public boolean isEmpty(){
        return (nItems ==0);
    }
    
    //返回隊(duì)列的大小
    public int getSize(){
        return nItems;
    }
    
}

測(cè)試

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數(shù)組數(shù)據(jù)為[1,2,3]
        
        System.out.println(queue.peekFront()); //1
        queue.remove();//queArray數(shù)組數(shù)據(jù)為[null,2,3]
        System.out.println(queue.peekFront()); //2
        
        queue.insert(4);//queArray數(shù)組數(shù)據(jù)為[4,2,3]
        queue.insert(5);//隊(duì)列已滿,queArray數(shù)組數(shù)據(jù)為[4,2,3]
    }

}

3蜜徽、雙端隊(duì)列

雙端隊(duì)列就是一個(gè)兩端都是結(jié)尾或者開(kāi)頭的隊(duì)列, 隊(duì)列的每一端都可以進(jìn)行插入數(shù)據(jù)項(xiàng)和移除數(shù)據(jù)項(xiàng)票摇,這些方法可以叫做:

insertRight()拘鞋、insertLeft()、removeLeft()矢门、removeRight()

如果嚴(yán)格禁止調(diào)用insertLeft()和removeLeft()(或禁用右端操作)盆色,那么雙端隊(duì)列的功能就和前面講的棧功能一樣。

如果嚴(yán)格禁止調(diào)用insertLeft()和removeRight(或相反的另一對(duì)方法)祟剔,那么雙端隊(duì)列的功能就和單向隊(duì)列一樣了隔躲。

4、優(yōu)先級(jí)隊(duì)列

優(yōu)先級(jí)隊(duì)列(priority queue)是比棧和隊(duì)列更專(zhuān)用的數(shù)據(jù)結(jié)構(gòu)物延,在優(yōu)先級(jí)隊(duì)列中宣旱,數(shù)據(jù)項(xiàng)按照關(guān)鍵字進(jìn)行排序,關(guān)鍵字最信咽怼(或者最大)的數(shù)據(jù)項(xiàng)往往在隊(duì)列的最前面浑吟,而數(shù)據(jù)項(xiàng)在插入的時(shí)候都會(huì)插入到合適的位置以確保隊(duì)列的有序。

優(yōu)先級(jí)隊(duì)列 是0個(gè)或多個(gè)元素的集合耗溜,每個(gè)元素都有一個(gè)優(yōu)先權(quán)组力,對(duì)優(yōu)先級(jí)隊(duì)列執(zhí)行的操作有:

(1)查找

(2)插入一個(gè)新元素

(3)刪除

一般情況下,查找操作用來(lái)搜索優(yōu)先權(quán)最大的元素抖拴,刪除操作用來(lái)刪除該元素 燎字。對(duì)于優(yōu)先權(quán)相同的元素,可按先進(jìn)先出次序處理或按任意優(yōu)先權(quán)進(jìn)行阿宅。

這里我們用數(shù)組實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列候衍,這種方法插入比較慢,但是它比較簡(jiǎn)單家夺,適用于數(shù)據(jù)量比較小并且不是特別注重插入速度的情況。

后面我們會(huì)講解堆伐弹,用堆的數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列拉馋,可以相當(dāng)快的插入數(shù)據(jù)。

數(shù)組實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列惨好,聲明為int類(lèi)型的數(shù)組煌茴,關(guān)鍵字是數(shù)組里面的元素,在插入的時(shí)候按照從大到小的順序排列日川,也就是越小的元素優(yōu)先級(jí)越高蔓腐。

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;
    }
    
    //插入數(shù)據(jù)
    public void insert(int value){
        int j;
        if(nItems == 0){
            priQueArray[nItems++] = value;
        }else{
            j = nItems -1;
            //選擇的排序方法是插入排序,按照從大到小的順序排列龄句,越小的越在隊(duì)列的頂端
            while(j >=0 && value > priQueArray[j]){
                priQueArray[j+1] = priQueArray[j];
                j--;
            }
            priQueArray[j+1] = value;
            nItems++;
        }
    }
    
    //移除數(shù)據(jù),由于是按照大小排序的回论,所以移除數(shù)據(jù)我們指針向下移動(dòng)
    //被移除的地方由于是int類(lèi)型的散罕,不能設(shè)置為null,這里的做法是設(shè)置為 -1
    public int remove(){
        int k = nItems -1;
        int value = priQueArray[k];
        priQueArray[k] = -1;//-1表示這個(gè)位置的數(shù)據(jù)被移除了
        nItems--;
        return value;
    }
    
    //查看優(yōu)先級(jí)最高的元素
    public int peekMin(){
        return priQueArray[nItems-1];
    }
    
    //判斷是否為空
    public boolean isEmpty(){
        return (nItems == 0);
    }
    
    //判斷是否滿了
    public boolean isFull(){
        return (nItems == maxSize);
    }

}

insert() 方法傀蓉,先檢查隊(duì)列中是否有數(shù)據(jù)項(xiàng)欧漱,如果沒(méi)有,則直接插入到下標(biāo)為0的單元里葬燎,否則误甚,從數(shù)組頂部開(kāi)始比較,找到比插入值小的位置進(jìn)行插入谱净,并把 nItems 加1.

remove 方法直接獲取頂部元素窑邦。

優(yōu)先級(jí)隊(duì)列的插入操作需要 O(N)的時(shí)間,而刪除操作則需要O(1) 的時(shí)間壕探,后面會(huì)講解如何通過(guò) 堆 來(lái)改進(jìn)插入時(shí)間冈钦。

5、總結(jié)

本篇博客我們介紹了隊(duì)列的三種形式浩蓉,分別是單向隊(duì)列派继、雙向隊(duì)列以及優(yōu)先級(jí)隊(duì)列。其實(shí)大家聽(tīng)名字也可以聽(tīng)得出來(lái)他們之間的區(qū)別捻艳,單向隊(duì)列遵循先進(jìn)先出的原則驾窟,而且一端只能插入,另一端只能刪除认轨。雙向隊(duì)列則兩端都可插入和刪除绅络,如果限制雙向隊(duì)列的某一段的方法,則可以達(dá)到和單向隊(duì)列同樣的功能嘁字。最后優(yōu)先級(jí)隊(duì)列恩急,則是在插入元素的時(shí)候進(jìn)行了優(yōu)先級(jí)別排序,在實(shí)際應(yīng)用中單項(xiàng)隊(duì)列和優(yōu)先級(jí)隊(duì)列使用的比較多纪蜒。后面講解了堆這種數(shù)據(jù)結(jié)構(gòu)衷恭,我們會(huì)用堆來(lái)實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列,改善優(yōu)先級(jí)隊(duì)列插入元素的時(shí)間纯续。

通過(guò)前面講的棧以及本篇講的隊(duì)列這兩種數(shù)據(jù)結(jié)構(gòu)随珠,我們稍微總結(jié)一下:

①、棧猬错、隊(duì)列(單向隊(duì)列)窗看、優(yōu)先級(jí)隊(duì)列通常是用來(lái)簡(jiǎn)化某些程序操作的數(shù)據(jù)結(jié)構(gòu),而不是主要作為存儲(chǔ)數(shù)據(jù)的倦炒。

②显沈、在這些數(shù)據(jù)結(jié)構(gòu)中,只有一個(gè)數(shù)據(jù)項(xiàng)可以被訪問(wèn)逢唤。

③拉讯、棧允許在棧頂壓入(插入)數(shù)據(jù)涤浇,在棧頂彈出(移除)數(shù)據(jù),但是只能訪問(wèn)最后一個(gè)插入的數(shù)據(jù)項(xiàng)遂唧,也就是棧頂元素芙代。

④、隊(duì)列(單向隊(duì)列)只能在隊(duì)尾插入數(shù)據(jù)盖彭,對(duì)頭刪除數(shù)據(jù)纹烹,并且只能訪問(wèn)對(duì)頭的數(shù)據(jù)。而且隊(duì)列還可以實(shí)現(xiàn)循環(huán)隊(duì)列召边,它基于數(shù)組铺呵,數(shù)組下標(biāo)可以從數(shù)組末端繞回到數(shù)組的開(kāi)始位置。

⑤隧熙、優(yōu)先級(jí)隊(duì)列是有序的插入數(shù)據(jù)片挂,并且只能訪問(wèn)當(dāng)前元素中優(yōu)先級(jí)別最大(或最小)的元素贞盯。

⑥音念、這些數(shù)據(jù)結(jié)構(gòu)都能由數(shù)組實(shí)現(xiàn),但是可以用別的機(jī)制(后面講的鏈表躏敢、堆等數(shù)據(jù)結(jié)構(gòu))實(shí)現(xiàn)闷愤。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市件余,隨后出現(xiàn)的幾起案子讥脐,更是在濱河造成了極大的恐慌,老刑警劉巖啼器,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旬渠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡端壳,警方通過(guò)查閱死者的電腦和手機(jī)告丢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)损谦,“玉大人岖免,你說(shuō)我怎么就攤上這事〕婶妫” “怎么了觅捆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵赦役,是天一觀的道長(zhǎng)麻敌。 經(jīng)常有香客問(wèn)我,道長(zhǎng)掂摔,這世上最難降的妖魔是什么术羔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任赢赊,我火速辦了婚禮,結(jié)果婚禮上级历,老公的妹妹穿的比我還像新娘释移。我一直安慰自己,他們只是感情好寥殖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布玩讳。 她就那樣靜靜地躺著,像睡著了一般嚼贡。 火紅的嫁衣襯著肌膚如雪熏纯。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天粤策,我揣著相機(jī)與錄音樟澜,去河邊找鬼。 笑死叮盘,一個(gè)胖子當(dāng)著我的面吹牛秩贰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播柔吼,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼毒费,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了嚷堡?” 一聲冷哼從身側(cè)響起蝗罗,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蝌戒,沒(méi)想到半個(gè)月后串塑,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡北苟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年桩匪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片友鼻。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡傻昙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出彩扔,到底是詐尸還是另有隱情妆档,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布虫碉,位于F島的核電站贾惦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜须板,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一碰镜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧习瑰,春花似錦绪颖、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至课兄,卻和暖如春滓鸠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背第喳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工糜俗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人曲饱。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓悠抹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親扩淀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子楔敌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容