【算法】排序(六)堆排序

正文之前

這一篇文章介紹一下堆的概念,以及堆排序

  1. 基本概念、最大/最小堆
  2. 堆排序
  3. 分析

正文

1. 什么是堆

堆不是單獨(dú)的一種結(jié)構(gòu),而是一種樹(shù)狀數(shù)據(jù)結(jié)構(gòu)攀甚,通過(guò)二叉堆來(lái)實(shí)現(xiàn),二叉堆也就是二叉樹(shù)的一種岗喉,講白了就是類(lèi)似二叉樹(shù)秋度,只不過(guò)滿足:

  • 除了最底層,其他層全滿钱床,最底層按照從左至右的順序填入節(jié)點(diǎn)(完全二叉樹(shù))

  • 父節(jié)點(diǎn)大于等于或小于等于子節(jié)點(diǎn)荚斯,這兩種情況就成為了最大堆/最小堆的定義:

最大堆:任意節(jié)點(diǎn)大于等于它的所有后裔(不僅是子節(jié)點(diǎn),還有子節(jié)點(diǎn)的子節(jié)點(diǎn)等等)查牌,最大節(jié)點(diǎn)在堆頂

最小堆:任意節(jié)點(diǎn)小于等于它的所有后裔事期,最小節(jié)點(diǎn)在堆頂

還有一種叫法是大頂堆/小頂堆

給個(gè)圖吧:

2. 堆排序

堆排序的步驟是這樣的:

  1. 用給定數(shù)組構(gòu)建堆
  2. 一個(gè)循環(huán),每次選擇堆頂元素纸颜,調(diào)換至末尾刑赶,然后縮小范圍(相當(dāng)于取出隊(duì)首元素,添加至末尾懂衩,一輪循環(huán)后,數(shù)組就有序了)

首先我們先來(lái)寫(xiě)一下確定節(jié)點(diǎn)位置的代碼:

    /**
     *  因?yàn)橄聵?biāo)是從 0 開(kāi)始的,在獲取節(jié)點(diǎn)
     *  的時(shí)候要多 + 1
     * @param node 給定節(jié)點(diǎn)的位置
     * @return 對(duì)應(yīng)節(jié)點(diǎn)位置
     */
    public int leftNode(int node){
        return 2 * node + 1;
    }
    public int rightNode(int node){
        return 2 * node + 2;
    }
    public int parentNode(int node){
        return (node - 1) / 2;
    }

這里的 parentNode() 方法沒(méi)有用到浊洞,但是這個(gè)計(jì)算父節(jié)點(diǎn)的方式下面要用到牵敷,然后我們來(lái)寫(xiě)調(diào)整堆的方法,采用遞歸的方法法希,每一次遞歸我們只針對(duì)某個(gè)節(jié)點(diǎn)以及它的子節(jié)點(diǎn):

    public void maxHeapify(int[] heap, int length, int node){
        //獲取左右節(jié)點(diǎn)
        int left = leftNode(node);
        int right = rightNode(node);
        //當(dāng)前最大節(jié)點(diǎn)(堆頂)
        int largest = node;
        //如果左子節(jié)點(diǎn)的值大于父節(jié)點(diǎn)的值枷餐,就改變 largest 的指向
        if (left < length && heap[left] > heap[largest]){
            largest = left;
        }
        //如果右子節(jié)點(diǎn)的值大于父節(jié)點(diǎn)的值,就改變 largest 的指向
        if (right < length && heap[right] > heap[largest]){
            largest = right;
        }
        //如果 largest 的指向改變了苫亦,就說(shuō)明要調(diào)整堆毛肋,調(diào)換兩個(gè)元素的位置
        if (largest != node){
            int temp = heap[node];
            heap[node] = heap[largest];
            heap[largest] = temp;
        } else {
            return;
        }
        maxHeapify(heap, length, largest);
    }

然后是構(gòu)建堆的方法,就像在上面的代碼中定義的尋找父節(jié)點(diǎn)位置的方法一樣屋剑,(heap.length - 1) 代表的是堆中最后一個(gè)節(jié)點(diǎn)位置润匙,(heap.length - 1) / 2 是最后一個(gè)節(jié)點(diǎn)的父節(jié)點(diǎn)的位置,從這里開(kāi)始構(gòu)建堆唉匾,避免多次不必要的遞歸孕讳,提高效率:

    public void buildHeap(int[] heap){
        for (int i = (heap.length - 1) / 2; i >= 0 ; i--) {
            maxHeapify(heap, heap.length, i);
        }
    }

最后便是堆排序的代碼:

    public void heapSort(int[] heap){
        for (int i = heap.length - 1; i > 0; i--) {
            int temp = heap[0];
            heap[0] = heap[i];
            heap[i] = temp;
            maxHeapify(heap, i, 0);
        }
    }

劃重點(diǎn):這里之所以要用 i 來(lái)代替 heap.length,是因?yàn)槲覀円M從堆頂刪除節(jié)點(diǎn)巍膘,實(shí)際上并沒(méi)有刪除厂财,只是把它換到了末尾,所以通過(guò)堆大小 - 1 來(lái)達(dá)到模擬刪除的效果

畫(huà)了個(gè)堆排序的過(guò)程圖:

數(shù)組 [3,5,2,6,4,1,10,7,9,8] 構(gòu)建完后就是圖中 1 的堆峡懈,然后把 4 和 10 對(duì)調(diào)璃饱,再調(diào)整堆,得到圖中 2 的堆肪康,把 6 和 9 對(duì)調(diào)荚恶,然后調(diào)整堆,以此類(lèi)推:

接下來(lái)是計(jì)算的結(jié)果:

構(gòu)建最大堆進(jìn)行排序的結(jié)果是升序梅鹦,因?yàn)榇蟮墓?jié)點(diǎn)在末尾裆甩,如果要降序排列,就構(gòu)建最小堆

3. 分析

  • 時(shí)間復(fù)雜度:

時(shí)間都花在了一開(kāi)始的構(gòu)建堆和接下來(lái)的調(diào)整堆過(guò)程齐唆,對(duì)于 n 個(gè)數(shù):

首先是新建堆嗤栓,時(shí)間復(fù)雜度 O(n)

每一次的調(diào)整堆都是 logn,總共有 n - 1 次箍邮,為 (n - 1) * logn = nlogn - logn

所以堆排序的時(shí)間復(fù)雜度是 O(nlogn)

  • 空間復(fù)雜度

O(1)茉帅,因?yàn)槭窃嘏判颍瑳](méi)有借助其它輔助結(jié)構(gòu)

  • 穩(wěn)定性

不穩(wěn)定

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锭弊,一起剝皮案震驚了整個(gè)濱河市堪澎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌味滞,老刑警劉巖樱蛤,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钮呀,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡昨凡,警方通過(guò)查閱死者的電腦和手機(jī)爽醋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)便脊,“玉大人蚂四,你說(shuō)我怎么就攤上這事∧奶担” “怎么了遂赠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)晌杰。 經(jīng)常有香客問(wèn)我跷睦,道長(zhǎng),這世上最難降的妖魔是什么乎莉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任送讲,我火速辦了婚禮,結(jié)果婚禮上惋啃,老公的妹妹穿的比我還像新娘哼鬓。我一直安慰自己,他們只是感情好边灭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布异希。 她就那樣靜靜地躺著,像睡著了一般绒瘦。 火紅的嫁衣襯著肌膚如雪称簿。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天惰帽,我揣著相機(jī)與錄音憨降,去河邊找鬼。 笑死该酗,一個(gè)胖子當(dāng)著我的面吹牛授药,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播呜魄,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼悔叽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了爵嗅?” 一聲冷哼從身側(cè)響起娇澎,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎睹晒,沒(méi)想到半個(gè)月后趟庄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體括细,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年戚啥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了勒极。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡虑鼎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出键痛,到底是詐尸還是另有隱情炫彩,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布絮短,位于F島的核電站江兢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏丁频。R本人自食惡果不足惜杉允,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望席里。 院中可真熱鬧叔磷,春花似錦、人聲如沸奖磁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)咖为。三九已至秕狰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間躁染,已是汗流浹背鸣哀。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吞彤,地道東北人我衬。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像备畦,于是被迫代替她去往敵國(guó)和親低飒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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