7,常見數(shù)據(jù)結構-堆

通常情況下我們把堆看成是一棵完全二叉樹罚随。堆一般分為兩種玉工,一種是最大堆,一種是最小堆淘菩。最大堆要求根節(jié)點的值即大于左子樹的值遵班,又大于右子樹的值。也就是說最大堆根節(jié)點的值是堆中最大的潮改。最小堆根節(jié)點的值是堆中最小的狭郑,前面我們講堆排序的時候介紹過堆,實際上他就是數(shù)組結構汇在,但因為數(shù)組中間有關聯(lián)翰萨,所以我們可以把它當做一棵完全二叉樹來看,下面我們再來看一下堆的數(shù)據(jù)結構是什么樣的糕殉。

在這里插入圖片描述

結點中的數(shù)字是數(shù)組元素的下標亩鬼,不是數(shù)組元素的值。所以如果我們知道父節(jié)點的下標我們就可以知道他的兩個子節(jié)點(如果有子節(jié)點)阿蝶,如果知道子節(jié)點的下標也一定能找到父節(jié)點的下標雳锋,他們的關系是:

父節(jié)點的下標=(子節(jié)點下標-1)>>1;
左子節(jié)點下標=父節(jié)點下標*2+1羡洁;
右子節(jié)點下標=父節(jié)點下標*2+2玷过;

堆的創(chuàng)建:

堆有兩種,一種是最大堆焚廊,一種是最小堆冶匹。顧名思義,最大堆就是堆頂?shù)脑厥亲畲蟮呐匚粒钚《丫褪嵌秧數(shù)脑厥亲钚〉慕腊T矶疾畈欢啵覀冎环治鲆粋€就行了袒餐,我們就以數(shù)組【10飞蛹,4谤狡,8,3卧檐,5墓懂,1】為例來畫個圖分析一下最小堆是怎么創(chuàng)建的。

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

這就是堆的創(chuàng)建霉囚,把元素加入到堆中的時候捕仔,都要和父節(jié)點進行比對,在最小堆中盈罐,如果比父節(jié)點小榜跌,就要和父節(jié)點交換,交換之后還要繼續(xù)和再和新的父節(jié)點對比……盅粪。同理在最大堆中钓葫,如果比父節(jié)點大,就要和父節(jié)點交換票顾。

我們看到上面堆創(chuàng)建之后數(shù)組的元素順序變?yōu)椤?础浮,4,3奠骄,10豆同,5,8】

堆的刪除

堆的添加會往上調整戚揭,堆的刪除不僅會往下調整而且還有可能往上調整诱告。堆中元素的刪除我們可以從堆頂刪除撵枢,也可以從中間某個位置刪除民晒,如果從堆頂刪除的話我們只需要往下調整即可,因為堆頂沒有父節(jié)點锄禽,不需要往上調整潜必。如果從中間刪除的話,我們先要往下調整沃但,如果往下調整沒有成功(比如在最小堆中磁滚,他比兩個子節(jié)點都小)宵晚,我們還要進行往上的調整垂攘。調整的原理就是把數(shù)組最后一個元素放到要刪除的元素位置上,然后在刪除元素的位置上進行上下調整淤刃,原理其實都差不多晒他,我們來看一下最小堆頂部元素刪除之后的調整。

在這里插入圖片描述

上面是我們把堆頂元素1刪除之后堆的調整逸贾,如果我們再把堆頂元素3刪除之后陨仅,只需要用數(shù)組的最后一個元素5替換3津滞,然后再往下調整即可……

代碼

堆的添加和刪除我們都分析過了,如果把前面的分析都搞懂的話灼伤,那么堆的代碼就很容易寫了触徐,我們來看下

public class MyHeap<E> {
    private Object[] data;//數(shù)據(jù)存放區(qū)
    private int size;//堆的大小
    private Comparator<? super E> comparator;//比較器

    public MyHeap(int initialCapacity) {
        this(initialCapacity, null);
    }

    public MyHeap(int initialCapacity, Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException("堆的大小必須大于0");
        this.data = new Object[initialCapacity];
        this.comparator = comparator;
    }

    /**
     * @param e 向堆中添加元素
     * @return
     */
    public boolean add(E e) {
        if (e == null)//不能為空
            throw new NullPointerException();
        if (size >= data.length)//如果堆的空間不夠了就擴容,這里是擴大2倍
            data = Arrays.copyOf(data, data.length << 1);
        if (size == 0)//如果堆是空的狐赡,直接添加就可以了撞鹉,不需要調整,因為就一個沒發(fā)調整
            data[0] = e;
        else//如果堆不是空的颖侄,就要往上調整孔祸。
            siftUp(e);
        size++;//添加完之后size要加1
        return true;
    }

    public int getSize() {
        return size;
    }

    //刪除堆頂元素
    public E remove() {
        if (size == 0)
            return null;
        size--;
        E result = (E) data[0];//獲取堆頂?shù)脑?        E x = (E) data[size];//取出數(shù)組的最后一個元素
        data[size] = null;//然后把最后一個元素的位置置空
        if (size != 0)
            siftDown(x);//這里實際上是把數(shù)組的最后一個元素取出放到棧頂,然后再往下調整发皿。
        return result;
    }

    //訪問堆頂元素崔慧,不刪除
    public E peek() {
        return (size == 0) ? null : (E) data[0];
    }

    /**
     * 返回數(shù)組的值
     *
     * @param a
     * @param <T>
     * @return
     */
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            return (T[]) Arrays.copyOf(data, size, a.getClass());
        System.arraycopy(data, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

    /**
     * 往上調整,往上調整只需要和父節(jié)點比較即可穴墅,如果比父節(jié)點大就不需要在調整
     *
     * @param e
     */
    private void siftUp(E e) {
        int s = size;
        while (s > 0) {
            int parent = (s - 1) >>> 1;//根據(jù)子節(jié)點的位置可以找到父節(jié)點的位置
            Object pData = data[parent];
            //和父節(jié)點比較惶室,如果比父節(jié)點大就退出循環(huán)不再調整
            if (comparator != null) {
                if (comparator.compare(e, (E) pData) >= 0)
                    break;
            } else {
                if (((Comparable<? super E>) e).compareTo((E) pData) >= 0)
                    break;
            }
            //如果比父節(jié)點小,就和父節(jié)點交換玄货,然后再繼續(xù)往上調整
            data[s] = pData;
            s = parent;
        }
        //通過上面的往上調整皇钞,找到合適的位置,再把e放進去
        data[s] = e;
    }

    /**
     * 往下調整松捉,往下調整需要和他的兩個子節(jié)點(如果有兩個子節(jié)點)都要比較夹界,哪個最小就和哪
     * 個交換,如果比兩個子節(jié)點都小就不用再交換
     *
     * @param e
     */
    private void siftDown(E e) {
        int half = size >>> 1;
        int index = 0;
        while (index < half) {
            int min = (index << 1) + 1;//根據(jù)父節(jié)點的位置可以找到左子節(jié)點的位置
            Object minChild = data[min];
            int right = min + 1;//根據(jù)左子節(jié)點找到右子節(jié)點的位置
            if (right < size) {//如果有右子節(jié)點就執(zhí)行這里的代碼
                //如果有右子節(jié)點隘世,肯定會有左子節(jié)點可柿。那么就需要左右兩個子節(jié)點比較,把小的賦值給minChild
                if (comparator != null) {
                    if (comparator.compare((E) minChild, (E) data[right]) > 0)
                        minChild = data[min = right];
                } else {
                    if (((Comparable<? super E>) minChild).compareTo((E) data[right]) > 0)
                        minChild = data[min = right];
                }
            }
            //用節(jié)點e和他的最小的子節(jié)點比較丙者,如果小于他最小的子節(jié)點就退出循環(huán)复斥,不再往下調整了,
            if (comparator != null) {
                if (comparator.compare(e, (E) minChild) <= 0)
                    break;
            } else {
                if (((Comparable<? super E>) e).compareTo((E) minChild) <= 0)
                    break;
            }
            //如果e比它的最小的子節(jié)點小械媒,就用最小的子節(jié)點和e交換位置目锭,然后再繼續(xù)往下調整。
            data[index] = minChild;
            index = min;
        }
        data[index] = e;
    }
}

這里的刪除和添加操作的都是堆頂?shù)脑胤桌蹋蕴砑拥臅r候會調用siftUp進行往上調整痢虹,刪除的時候會調用siftDown進行往下調整。我把注釋都寫在代碼中了主儡,這里就不再詳細介紹奖唯。

堆排序

通過上面的分析,我們知道最大堆就是堆頂元素始終保持的是最大值缀辩,最小堆就是堆頂元素始終保持的是最小值臭埋,假如在最小堆中我們把堆頂元素一個個給移除不就相當于排序了嗎踪央。我們來測試一下

   int[] array = {10, 4, 8, 3, 5, 1};
   System.out.print("數(shù)組原始的值:");
   Util.printIntArrays(array);
   MyHeap myHeap = new MyHeap(10, new Comparator<Integer>() {
       @Override
       public int compare(Integer o, Integer t1) {
           return (o - t1 > 0) ? 1 : -1;
       }
   });

   for (int i = 0; i < array.length; i++) {
       myHeap.add(array[i]);
   }
   System.out.println();
   System.out.print("堆中元素的值:");
   Util.printObjectArrays(myHeap.toArray(new Object[myHeap.getSize()]));
   System.out.println();
   System.out.print("排序之后的值:");
   for (int i = 0, length = myHeap.getSize(); i < length; i++) {
       System.out.print(myHeap.remove() + "\t");
   }

我們來看一下打印結果

1數(shù)組原始的值:10    4   8   3   5   1
2堆中元素的值:1    4   3   10  5   8
3排序之后的值:1    3   4   5   8   10

我們看到堆中元素的值是【1,4瓢阴,3畅蹂,10,5荣恐,8】和我們最開始分析創(chuàng)建堆的結果完全一致液斜。并且最后一行完成了從小到大的順序輸出

總結:

上面我們主要分析是最小堆,如果是最大堆代碼該怎么寫呢叠穆,其實有兩種方式少漆,一種是直接在上面代碼MyHeap類中修改,還一種是在創(chuàng)建構造函數(shù)的時候重寫比較器Comparator硼被,我們這里推薦使用第二種示损,比如我們想按照從大到小的順序輸出,我們來看下該怎么寫

    int[] array = {10, 4, 8, 3, 5, 1};
    System.out.print("數(shù)組原始的值:");
    Util.printIntArrays(array);
    MyHeap myHeap = new MyHeap(10, new Comparator<Integer>() {
        @Override
        public int compare(Integer o, Integer t1) {
            return (o - t1 < 0) ? 1 : -1;
        }
    });

    for (int i = 0; i < array.length; i++) {
        myHeap.add(array[i]);
    }
    System.out.println();
    System.out.print("堆中元素的值:");
    Util.printObjectArrays(myHeap.toArray(new Object[myHeap.getSize()]));
    System.out.println();
    System.out.print("排序之后的值:");
    for (int i = 0, length = myHeap.getSize(); i < length; i++) {
        System.out.print(myHeap.remove() + "\t");
    }

我們只需修改一個字符即可嚷硫,就是在上面第7行把之前的大于號改為小于號检访,我們來看下運行結果

數(shù)組原始的值:10    4   8   3   5   1
堆中元素的值:10    5   8   3   4   1
排序之后的值:10    8   5   4   3   1

最后完全實現(xiàn)了從大到小的輸出僧诚。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末吵护,一起剝皮案震驚了整個濱河市糕篇,隨后出現(xiàn)的幾起案子华糖,更是在濱河造成了極大的恐慌,老刑警劉巖售躁,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件朝刊,死亡現(xiàn)場離奇詭異残炮,居然都是意外死亡负懦,警方通過查閱死者的電腦和手機筒捺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來密似,“玉大人焙矛,你說我怎么就攤上這事〔须纾” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵贫导,是天一觀的道長抛猫。 經常有香客問我,道長孩灯,這世上最難降的妖魔是什么闺金? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮峰档,結果婚禮上败匹,老公的妹妹穿的比我還像新娘寨昙。我一直安慰自己,他們只是感情好掀亩,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布舔哪。 她就那樣靜靜地躺著,像睡著了一般槽棍。 火紅的嫁衣襯著肌膚如雪捉蚤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天炼七,我揣著相機與錄音缆巧,去河邊找鬼。 笑死豌拙,一個胖子當著我的面吹牛陕悬,可吹牛的內容都是我干的。 我是一名探鬼主播按傅,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼墩莫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了逞敷?” 一聲冷哼從身側響起狂秦,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎推捐,沒想到半個月后裂问,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡牛柒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年堪簿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片皮壁。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡椭更,死狀恐怖,靈堂內的尸體忽然破棺而出蛾魄,到底是詐尸還是另有隱情虑瀑,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布滴须,位于F島的核電站舌狗,受9級特大地震影響,放射性物質發(fā)生泄漏扔水。R本人自食惡果不足惜痛侍,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望魔市。 院中可真熱鬧主届,春花似錦赵哲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谈截,卻和暖如春筷屡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背簸喂。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工毙死, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喻鳄。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓扼倘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親除呵。 傳聞我的和親對象是個殘疾皇子再菊,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355