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

優(yōu)先級(jí)隊(duì)列概念

前面介紹過(guò)隊(duì)列斗搞,隊(duì)列是一種先進(jìn)先出(FIFO)的數(shù)據(jù)結(jié)構(gòu)指攒,但有些情況下,操作的數(shù)據(jù)可能帶有優(yōu)先級(jí)僻焚,一般出隊(duì)列時(shí)允悦,可能需要優(yōu)先級(jí)高的元素先出隊(duì)列,該中場(chǎng)景下虑啤,使用隊(duì)列顯然不合適隙弛,比如:在手機(jī)上玩游戲的時(shí)候,如果有來(lái)電狞山,那么系統(tǒng)應(yīng)該優(yōu)先處理打進(jìn)來(lái)的電話全闷。在這種情況下,我們的數(shù)據(jù)結(jié)構(gòu)應(yīng)該提供兩個(gè)最基本的操作萍启,一個(gè)是返回最高優(yōu)先級(jí)對(duì)象总珠,一個(gè)是添加新的對(duì)象。這種數(shù)據(jù)結(jié)構(gòu)就是優(yōu)先級(jí)隊(duì)列(Priority Queue)

PriorityQueue的特性

Java集合框架中提供了PriorityQueue和PriorityBlockingQueue兩種類型的優(yōu)先級(jí)隊(duì)列勘纯,PriorityQueue是線程不安全的局服,PriorityBlockingQueue是線程安全的,本文主要介紹PriorityQueue驳遵。
使用PriorityQueue注意事項(xiàng):

  • 使用PriorityQueue必須要導(dǎo)PriorityQueue所在的包
import.java.util.PriorityQueue;
  • PriorityQueue所插入的元素必須要能比較大小淫奔,否則會(huì)拋出異常ClassCastExcepetion異常
  • 不能插入null對(duì)象,否則會(huì)拋出NullPointerException
  • 沒(méi)有容量限制堤结,可以插入任意個(gè)元素唆迁,會(huì)自動(dòng)擴(kuò)容
  • 插入和刪除元素的時(shí)間復(fù)雜度為 o(log2N)
  • PriorityQueue底層使用了堆數(shù)據(jù)結(jié)構(gòu)

優(yōu)先級(jí)隊(duì)列的構(gòu)造

構(gòu)造器 功能介紹
PriorityQueue() 創(chuàng)建一個(gè)空的優(yōu)先級(jí)隊(duì)列,默認(rèn)容量是11
PriorityQueue(int initialCapacity) 創(chuàng)建一個(gè)初始容量為initialCapacity的優(yōu)先級(jí)隊(duì)列竞穷,注意:initialCapacity不能小于1唐责,否則會(huì)拋IllegalArgumentExcepetion
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;

public class Demo {
    public static void main(String[] args) {
        //創(chuàng)建一個(gè)空的優(yōu)先級(jí)隊(duì)列,默認(rèn)容量是11
        PriorityQueue<Integer> q1 = new PriorityQueue<>();
        //創(chuàng)建一個(gè)容量為100的空優(yōu)先級(jí)隊(duì)列
        PriorityQueue<Integer> q2 = new PriorityQueue<>(100);

        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(5);

        PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
        System.out.println(q3.size());
        System.out.println(q3.peek());
    }
}
image.png

插入来庭、刪除妒蔚、獲取優(yōu)先級(jí)最高的元素

函數(shù)名 功能介紹
boolean offer(E e) 插入元素,插入成功返回true,若e為null肴盏,會(huì)拋出NullPointerException異常科盛,時(shí)間復(fù)雜度為O(log2N),注意若空間不夠會(huì)自動(dòng)擴(kuò)容
E peek() 獲取優(yōu)先級(jí)最高的元素菜皂,若隊(duì)列為空贞绵,則返回null
E poll() 移除優(yōu)先級(jí)最高的元素,若隊(duì)列為空恍飘,則返回null
int size() 獲取有效元素個(gè)數(shù)
void clear() 清空
boolean isEmpty() 檢查優(yōu)先級(jí)隊(duì)列是否為空榨崩,若為空,則返回true
package xsk;

import org.omg.CORBA.ARG_OUT;

import javax.sound.midi.Soundbank;
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;

public class Demo {
    public static void main(String[] args) {
        //創(chuàng)建一個(gè)空的優(yōu)先級(jí)隊(duì)列章母,默認(rèn)容量是11
        PriorityQueue<Integer> q1 = new PriorityQueue<>();
        //創(chuàng)建一個(gè)容量為100的空優(yōu)先級(jí)隊(duì)列
        PriorityQueue<Integer> q2 = new PriorityQueue<>(100);

        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(5);

        PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
        System.out.println(q3.size());
        System.out.println(q3.peek());

        q3.poll();
        System.out.println(q3.size());
        System.out.println(q3.peek());

        q3.clear();
        if(q3.isEmpty()) {
            System.out.println("隊(duì)列為空");
        } else {
            System.out.println("隊(duì)列不為空");
        }

        q3.offer(1);
        System.out.println(q3.peek());
        System.out.println(q3.peek());

    }
}

堆是一種特殊的二叉樹:

  • 堆是一個(gè)完全二叉樹
  • 堆采用順序存儲(chǔ)的方式
  • 堆中任一結(jié)點(diǎn)都滿足比子節(jié)點(diǎn)的值大(大堆)或比子節(jié)點(diǎn)的值心钢搿(小堆)
    我們來(lái)看一個(gè)大堆在內(nèi)存的存儲(chǔ)方式

image.png

image.png

從上圖可以看出,父結(jié)點(diǎn)和子節(jié)點(diǎn)的關(guān)系:
已知父節(jié)點(diǎn)的下標(biāo)為x,則孩子節(jié)點(diǎn)的下標(biāo):

  • 左孩子下標(biāo):2 * x + 1;
  • 右孩子下標(biāo):2 * x + 2;

已知孩子下標(biāo)為x,則父節(jié)點(diǎn)的下標(biāo)為:

  • 左孩子下標(biāo)為x 時(shí)乳怎,父節(jié)點(diǎn)的下標(biāo):( x - 1 ) / 2;
  • 右孩子下標(biāo)為x時(shí)彩郊,父節(jié)點(diǎn)的下標(biāo):( x - 1 ) / 2 或 ( x - 2 ) / 2;

堆的向下調(diào)整



image.png

仔細(xì)觀察上圖后發(fā)現(xiàn):根節(jié)點(diǎn)的左右子樹已經(jīng)完全滿足堆的性質(zhì),因此只需將根節(jié)點(diǎn)向下調(diào)整好即可蚪缀。
向下過(guò)程(以小堆為例):

  • 1. 讓parent標(biāo)記需要調(diào)整的節(jié)點(diǎn)秫逝,child標(biāo)記parent的左孩子(注意:parent如果有孩子一定先是有左孩子)
  • 2. 如果parent的左孩子存在,即:child < size询枚, 進(jìn)行以下操作违帆,直到parent的左孩子不存在:
    • parent右孩子是否存在,存在找到左右孩子中最小的孩子金蜀,讓child進(jìn)行比較
    • 將parent與較小的孩子child比較刷后,如果
      * parent小于較小的孩子child,調(diào)整結(jié)束
      * 否則:交換parent與較小的孩子child廉油,交換完成之后惠险,parent中大的元素
      向下移動(dòng),可能導(dǎo)致子樹不滿足堆的性質(zhì)抒线,因此需要繼續(xù)向下調(diào)整,即
      parent = child; child = parent * 2 + 1;然后繼續(xù)2的過(guò)程

向下調(diào)整(小堆):

 public void shiftDown(int[] arr, int size, int index) {
        int parent = index;
        int child = 2 * parent + 1;
        while(child < size) {
            if (child + 1 < size && arr[child + 1] < arr[child]) {
                child = child + 1;
            }
            if(arr[parent] > arr[child]) {
                int tmp = arr[child];
                arr[child] = arr[parent];
                arr[parent] = tmp;
            } else {
                break;
            }
            parent = child;
            child = 2 * parent + 1;
        }
    }
}

注意:在調(diào)整以parent為根的二叉樹時(shí)渣慕,必須要滿足parent的左子樹和右子樹已經(jīng)是堆了才可以向下調(diào)整嘶炭。

堆的創(chuàng)建

public static void createHeap(int[] array) {
    // 找倒數(shù)第一個(gè)非葉子節(jié)點(diǎn),從該節(jié)點(diǎn)位置開始往前一直到根節(jié)點(diǎn)逊桦,遇到一個(gè)節(jié)點(diǎn)眨猎,應(yīng)用向下調(diào)整
    for(int i = arr.length - 1; i >= 0; i--) {
                shiftDown(arr, arr.length, i);
            }
   }

堆的插入

插入堆需要兩個(gè)步驟

  • 先將元素放在底層空間中
  • 將最后放入的元素向上調(diào)整,直到滿足堆的性質(zhì)


public class MyPriorityQueue {
    public static void shiftUp(int[] arr, int size, int index) {
        int child = index;
        int parent = (child - 1) / 2;
        while(child > 0) {
            if(arr[child] > arr[parent]) {
                int tmp = arr[child];
                arr[child] = arr[parent];
                arr[parent] = tmp;
            } else {
                break;
            }
            child = parent;
            parent = (child - 1) / 2;

        }
    }
    public static void creatHeap(int[] arr) {
        for(int i = arr.length - 1; i >= 0; i--) {
            shiftUp(arr, arr.length, i);
        }
    }
    public int[] arr = new int[100];
    public int size = 0;

    public void offer(int val) {
        if(size >= arr.length) {
            return;
        }
        arr[size] = val;
        size++;
        shiftUp(arr, size, size - 1);
    }
}

堆的刪除

注意:堆刪除的一定是堆頂元素强经,具體操作如下:

  • 將堆頂元素與堆的最后一個(gè)元素交換
  • 堆中有效數(shù)據(jù)個(gè)數(shù)減一
  • 堆頂元素進(jìn)行向下調(diào)整


    image.png
public void poll(int val) {
        if(size == 0) {
            return;
        }
        int result = arr[0];
        arr[0] = arr[size - 1];
        arr[size - 1] = result;
        size--;
        shiftUp(arr, size, 0);
    }

用堆實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列

package xsk;

public class MyPriorityQueue {
    public static void shiftUp(int[] arr, int size, int index) {
        int child = index;
        int parent = (child - 1) / 2;
        while(child > 0) {
            if(arr[child] > arr[parent]) {
                int tmp = arr[child];
                arr[child] = arr[parent];
                arr[parent] = tmp;
            } else {
                break;
            }
            child = parent;
            parent = (child - 1) / 2;

        }
    }
    public static void creatHeap(int[] arr) {
        for(int i = arr.length - 1; i >= 0; i--) {
            shiftUp(arr, arr.length, i);
        }
    }
    public int[] arr = new int[100];
    public int size = 0;

    public void offer(int val) {
        if(size >= arr.length) {
            return;
        }
        arr[size] = val;
        size++;
        shiftUp(arr, size, size - 1);
    }
    public void poll(int val) {
        if(size == 0) {
            return;
        }
        int result = arr[0];
        arr[0] = arr[size - 1];
        arr[size - 1] = result;
        size--;
        shiftUp(arr, size, 0);
    }
    public Integer peek() {
        if(size == 0) {
            return null;
        }
        return arr[0];
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末睡陪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兰迫,老刑警劉巖信殊,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異汁果,居然都是意外死亡涡拘,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門据德,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鳄乏,“玉大人,你說(shuō)我怎么就攤上這事棘利〕饕埃” “怎么了?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵善玫,是天一觀的道長(zhǎng)仲吏。 經(jīng)常有香客問(wèn)我,道長(zhǎng)蝌焚,這世上最難降的妖魔是什么裹唆? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮只洒,結(jié)果婚禮上许帐,老公的妹妹穿的比我還像新娘。我一直安慰自己毕谴,他們只是感情好成畦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涝开,像睡著了一般循帐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舀武,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天拄养,我揣著相機(jī)與錄音,去河邊找鬼银舱。 笑死瘪匿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的寻馏。 我是一名探鬼主播棋弥,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼诚欠!你這毒婦竟也來(lái)了顽染?” 一聲冷哼從身側(cè)響起漾岳,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎粉寞,沒(méi)想到半個(gè)月后尼荆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仁锯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年耀找,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片业崖。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡野芒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出双炕,到底是詐尸還是另有隱情狞悲,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布妇斤,位于F島的核電站摇锋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏站超。R本人自食惡果不足惜荸恕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望死相。 院中可真熱鬧融求,春花似錦、人聲如沸算撮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肮柜。三九已至陷舅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間审洞,已是汗流浹背莱睁。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留预明,地道東北人缩赛。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像撰糠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子辩昆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

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