堆(Heap)和有優(yōu)先隊列(Priority Queue)

1 優(yōu)先隊列(Priority Queue)
優(yōu)先隊列與普通隊列的區(qū)別:普通隊列遵循先進先出的原則;優(yōu)先隊列的出隊順序與入隊順序無關(guān)沸久,與優(yōu)先級相關(guān)季眷。
優(yōu)先隊列可以使用隊列的接口,只是在實現(xiàn)接口時卷胯,與普通隊列有兩處區(qū)別子刮,一處在于優(yōu)先隊列出隊的元素應(yīng)該是優(yōu)先級最高的元素,另一處在于隊首元素也是優(yōu)先級最高的元素窑睁。
優(yōu)先隊列也可以使用不同的底層實現(xiàn)挺峡,不同底層實現(xiàn)的時間復(fù)雜度如下:


image.png

從上圖可以看出,使用"堆"這種數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)優(yōu)先隊列是比較高效的担钮。

2 二叉堆(Binary Heap)
二叉堆就是一棵滿足特殊性質(zhì)的二叉樹
首先橱赠,二叉堆是一棵完全二叉樹,"完全二叉樹"箫津,不一定是滿二叉樹狭姨,不滿的部分一定位于整棵樹的右下側(cè)。
其次苏遥,堆中某個節(jié)點的值總是不大于其父節(jié)點的值(最大堆)饼拍;相應(yīng)的,堆中的某個節(jié)點的值總是不小于其父節(jié)點的值(最小堆)田炭。
節(jié)點值的大小與其所處的層次沒有必然聯(lián)系师抄,即,最大堆中诫肠,只需保證每個節(jié)點不大于其父節(jié)點即可司澎,至于大不大于其父節(jié)點的兄弟節(jié)點,沒有任何關(guān)系栋豫。
可以用數(shù)組來存儲二叉堆挤安,如下圖所示:


image.png

用動態(tài)數(shù)組實現(xiàn)二叉堆的業(yè)務(wù)邏輯如下:

    public class MaxHeap<E extends Comparable<E>> {

        private Array<E> data = new Array<>();

        // 構(gòu)造函數(shù)
        public MaxHeap(int capacity) {
            data = new Array<>(capacity);
        }

        // 無參數(shù)構(gòu)造函數(shù)
        public MaxHeap() {
            data = new Array<>();
        }

        // 接收參數(shù)為數(shù)組的構(gòu)造函數(shù)
        public MaxHeap(E[] arr) {
            data = new Array<>(arr);
            for (int i = parent(arr.length - 1); i >= 0; i--) {
                SiftDown(i);
            }
        }

        // 實現(xiàn)getSize方法,返回堆中的元素個數(shù)
        public int getSize() {
            return data.getSize();
        }

        // 實現(xiàn)isEmpty方法丧鸯,返回堆是否為空
        public boolean isEmpty() {
            return data.isEmpty();
        }

        // 返回完全二叉樹的數(shù)組表示中蛤铜,一個索引所表示的元素的父節(jié)點的索引
        private int parent(int index) {
            if (index == 0) {
                throw new IllegalArgumentException("Index-0 doesn't have parent.");
            }
            return (index - 1) / 2;
        }

        // 返回完全二叉樹的數(shù)組表示中,一個索引所表示的元素的左孩子的索引
        private int leftChild(int index) {
            return index * 2 + 1;
        }

        // 返回完全二叉樹的數(shù)組表示中,一個索引所表示的元素的右孩子的索引
        private int rightChild(int index) {
            return index * 2 + 2;
        }

        // 實現(xiàn)add方法围肥,向堆中添加元素
        public void add(E e) {
            data.addLast(e);
            SiftUp(data.getSize() - 1);
        }

        // 實現(xiàn)元素的上浮
        private void SiftUp(int k) {
            while (k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0) {
                data.swap(k, parent(k));
                k = parent(k);
            }
        }

        // 實現(xiàn)findMax方法剿干,查看堆中的最大元素
        public E findMax() {
            if (data.getSize() == 0) {
                throw new IllegalArgumentException("Can not findMax when heap is empty.");
            }
            return data.get(0);
        }

        // 實現(xiàn)extractMax方法,取出堆中的最大元素
        public E extractMax() {
            E ret = findMax();
            data.swap(0, data.getSize() - 1);
            data.removeLast();
            SiftDown(0);
            return ret;
        }

        // 實現(xiàn)元素的下沉
        private void SiftDown(int k) {
            while (leftChild(k) < data.getSize()) {
                int j = leftChild(k);
                if (j + 1 < data.getSize() && data.get(j + 1).compareTo(data.get(j)) > 0) {
                    j = rightChild(k);
                    // data[j]是leftChild和rightChild中的對大值
                }
                if (data.get(k).compareTo(data.get(j)) >= 0) {
                    break;
                } else {
                    data.swap(k, j);
                    k = j;
                }
            }
        }

        // 實現(xiàn)replace方法穆刻,取出堆中的最大元素置尔,并替換為元素e
        public E replace(E e) {
            E ret = findMax();
            data.set(0, e);
            SiftDown(0);
            return ret;
        }
    }

測試用動態(tài)數(shù)組實現(xiàn)的二叉堆

    import java.util.Random;

    public class Main {

        public static void main(String[] args) {

            int n = 1000000;
            MaxHeap<Integer> maxHeap = new MaxHeap<>();
            Random random = new Random();
            for (int i = 0; i < n; i++) {
                maxHeap.add(random.nextInt(Integer.MAX_VALUE));
            }

            int[] arr = new int[n];
            for (int i = 0; i < n; i++) {
                arr[i] = maxHeap.extractMax();
            }

            for (int i = 1; i < n; i++) {
                if (arr[i - 1] < arr[i]) {
                    throw new IllegalArgumentException("Error");
                }
            }

            System.out.println("Test MaxHeap completed.");
        }
    }

二叉堆的時間復(fù)雜度分析


image.png

由于堆是一棵完全二叉樹,所以堆不會退化成鏈表氢伟。

3.用最大堆實現(xiàn)一個優(yōu)先隊列(Priority Queue)

實現(xiàn)優(yōu)先隊列的業(yè)務(wù)邏輯如下:

    public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {

        private MaxHeap<E> maxHeap;

        // 構(gòu)造函數(shù)
        public PriorityQueue() {
            maxHeap = new MaxHeap<>();
        }

        // 實現(xiàn)getSize方法
        @Override
        public int getSize() {
            return maxHeap.getSize();
        }

        // 實現(xiàn)isEmpty方法
        @Override
        public boolean isEmpty() {
            return maxHeap.isEmpty();
        }

        // 實現(xiàn)getFront方法
        @Override
        public E getFront() {
            return maxHeap.findMax();
        }

        // 實現(xiàn)enqueue方法
        @Override
        public void enqueue(E e) {
            maxHeap.add(e);
        }

        // 實現(xiàn)dequeue方法
        @Override
        public E dequeue() {
            return maxHeap.extractMax();
        }
    }

4.優(yōu)先隊列的應(yīng)用:從N個元素中榜轿,選出前M個
解決方案:使用優(yōu)先隊列,維護當前的M個元素朵锣,然后不斷更新元素谬盐,直到掃描完所有N個元素。
需要使用"最小堆"來進行底層的實現(xiàn)诚些,因為最終獲取的是前M個元素飞傀,通過最小堆的extractMin方法,可以不斷的剔除堆中的最小元素
也可以使用最大堆來實現(xiàn)诬烹,我們只要規(guī)定元素越小砸烦,優(yōu)先級越高。
使用最小堆實現(xiàn)的業(yè)務(wù)邏輯如下:

    import java.util.List;
    import java.util.PriorityQueue;
    import java.util.TreeMap;

    public class Solution2 {

        private class Freq implements Comparable<Freq> {

            public int e, freq;

            public Freq(int e, int freq) {
                this.e = e;
                this.freq = freq;
            }

            public int compareTo(Freq another) {
                if (this.freq < another.freq)
                    return -1;
                else if (this.freq > another.freq)
                    return 1;
                else
                    return 0;
            }
        }

        public List<Integer> topKFrequent(int[] nums, int k) {

            TreeMap<Integer, Integer> map = new TreeMap<>();
            for (int num : nums) {
                if (map.containsKey(num))
                    map.put(num, map.get(num) + 1);
                else
                    map.put(num, 1);
            }

            PriorityQueue<Freq> pq = new PriorityQueue<>();
            for (int key : map.keySet()) {
                if (pq.size() < k)
                    pq.add(new Freq(key, map.get(key)));
                else if (map.get(key) > pq.peek().freq) {
                    pq.remove();
                    pq.add(new Freq(key, map.get(key)));
                }
            }

            LinkedList<Integer> res = new LinkedList<>();
            while (!pq.isEmpty())
                res.add(pq.remove().e);
            return res;
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末椅您,一起剝皮案震驚了整個濱河市外冀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌掀泳,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件西轩,死亡現(xiàn)場離奇詭異员舵,居然都是意外死亡,警方通過查閱死者的電腦和手機藕畔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門马僻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人注服,你說我怎么就攤上這事韭邓。” “怎么了溶弟?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵女淑,是天一觀的道長。 經(jīng)常有香客問我辜御,道長鸭你,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮袱巨,結(jié)果婚禮上阁谆,老公的妹妹穿的比我還像新娘。我一直安慰自己愉老,他們只是感情好场绿,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嫉入,像睡著了一般裳凸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上劝贸,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天姨谷,我揣著相機與錄音,去河邊找鬼映九。 笑死梦湘,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的件甥。 我是一名探鬼主播捌议,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼朋蔫,長吁一口氣:“原來是場噩夢啊……” “哼棚饵!你這毒婦竟也來了逊移?” 一聲冷哼從身側(cè)響起拗秘,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤倘感,失蹤者是張志新(化名)和其女友劉穎簇捍,沒想到半個月后禽炬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碎节,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡曾我,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年粉怕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抒巢。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡贫贝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蛉谜,到底是詐尸還是另有隱情稚晚,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布型诚,位于F島的核電站客燕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏俺驶。R本人自食惡果不足惜幸逆,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一棍辕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧还绘,春花似錦楚昭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至昔案,卻和暖如春尿贫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背踏揣。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工庆亡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捞稿。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓又谋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親娱局。 傳聞我的和親對象是個殘疾皇子彰亥,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

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