編程馬拉松 Day05 堆释液、二叉堆全释、堆排序

堆排序需要用到二叉堆,在開始之前均澳,我們先來(lái)了解一下什么是二叉堆恨溜。

當(dāng)二叉樹滿足滿足如下條件時(shí),我們說(shuō)這個(gè)二叉樹是堆有序的:

  1. 每一個(gè)父結(jié)點(diǎn)的值都比它的子結(jié)點(diǎn)大(稱為大頂堆)或姓仪啊(稱為小頂堆)
  2. 子結(jié)點(diǎn)的大小與其左右位置無(wú)關(guān)

堆有序的二叉樹糟袁,也可稱為二叉堆。二叉堆是最常見的堆結(jié)構(gòu)躺盛,因此也常將二叉堆直接稱為项戴,可以采用如下兩種方式來(lái)表示二叉堆

  1. 使用指針,二叉樹的每個(gè)結(jié)點(diǎn)需存儲(chǔ)三個(gè)指針槽惫,分別指向其父結(jié)點(diǎn)和兩個(gè)子結(jié)點(diǎn)
  2. 使用數(shù)組周叮,對(duì)二叉樹做層序遍歷辩撑,按層級(jí)順序放入數(shù)組中,根結(jié)點(diǎn)在數(shù)組索引0的位置存放仿耽,其子結(jié)點(diǎn)分別在索引1和2的位置合冀,1和2個(gè)子結(jié)點(diǎn)分別在位置3、4和5项贺、6中存放君躺,以此類推

就排序來(lái)講,其所需處理的數(shù)據(jù)較為連續(xù)开缎,沒有空隙棕叫,可用完全二叉樹來(lái)表示。對(duì)于完全二叉樹奕删,采用數(shù)組的表示方法也更方便些俺泣,下圖展示了采用數(shù)組實(shí)現(xiàn)的兩個(gè)二叉堆。


二叉堆

對(duì)于數(shù)組實(shí)現(xiàn)的二叉堆完残,索引為k的結(jié)點(diǎn)的父結(jié)點(diǎn)的索引為(k-1)/2伏钠,它的子結(jié)點(diǎn)的索引分別為2k+1和2k+2。

堆有序化

以大頂堆為例谨设,有序化的過(guò)程中我們會(huì)遇到兩種情況

  1. 在堆底加入一個(gè)較大元素時(shí)贝润,我們需要由下至上恢復(fù)堆的順序
  2. 當(dāng)將根結(jié)點(diǎn)替換為一個(gè)較小元素時(shí),我們需要由上到下恢復(fù)堆的順序

由下至上的堆有序化(上嘎料)

如果堆的有序狀態(tài)因?yàn)槟硞€(gè)結(jié)點(diǎn)變的比它的父結(jié)點(diǎn)更大而被打破打掘,就需要通過(guò)將它與它的父結(jié)點(diǎn)交換來(lái)恢復(fù)堆有序。交換后鹏秋,這個(gè)結(jié)點(diǎn)比它的兩個(gè)子結(jié)點(diǎn)都大尊蚁,但這個(gè)結(jié)點(diǎn)仍然可能比它現(xiàn)在的父結(jié)點(diǎn)更大。我們可以一遍遍的用同樣的方式來(lái)將其向上移動(dòng)侣夷,直到遇到一個(gè)比它更大的父結(jié)點(diǎn)或到達(dá)了堆的根結(jié)點(diǎn)横朋,如下圖所示。


由下至上的堆有序化(上赴偻亍)

上浮操作對(duì)應(yīng)的代碼如下

private void swim(Integer arr[], int k) {
    while(k > 0 && arr[(k - 1) / 2] < arr[k]) { //若k>0且索引為k的結(jié)點(diǎn)大于其父結(jié)點(diǎn)時(shí)琴锭,將該結(jié)點(diǎn)與其父結(jié)點(diǎn)交換
        swap(arr, k, (k - 1) / 2);
        k = (k - 1) / 2;
    }
}

由上至下的堆有序化(下沉)

如果堆的有序狀態(tài)因?yàn)槟硞€(gè)結(jié)點(diǎn)變的比它的某個(gè)子結(jié)點(diǎn)更小而被打破,就需要通過(guò)將它和它的子結(jié)點(diǎn)中較大者交換位置來(lái)恢復(fù)堆有序衙传。交換可能會(huì)在子結(jié)點(diǎn)處繼續(xù)打破堆的有序狀態(tài)决帖,此時(shí)可以采用相同的方式,將結(jié)點(diǎn)向下移動(dòng)直到它的子結(jié)點(diǎn)都比它小或是到達(dá)了堆的底部蓖捶,如下圖所示地回。


由上至下的堆有序化(下沉)

下沉操作對(duì)應(yīng)的代碼如下

private void sink(Integer arr[], int k) {
    while(2 * k + 1 <= arr.length - 1) {//若k存在子結(jié)點(diǎn),則進(jìn)入循環(huán)
        int j = 2 * k + 1; //獲取k的第一個(gè)子結(jié)點(diǎn)
        if (j < arr.length - 1 && arr[j] < arr[j + 1]) {//若存在兩個(gè)子結(jié)點(diǎn),則找到其中較大的子結(jié)點(diǎn)
            j++;
        }
        if (arr[j] > arr[k]) {//若k的較大子結(jié)點(diǎn)比k大刻像,則交換它們的位置
            swap(arr, j, k);
            k = j;
        }
    }
}

堆排序

在介紹完堆的數(shù)據(jù)結(jié)構(gòu)和操作方式后畅买,我們來(lái)看堆排序是如何進(jìn)行的。

堆的構(gòu)造

  1. 將原數(shù)組看做堆的話细睡,則最后一個(gè)分支結(jié)點(diǎn)(含有子結(jié)點(diǎn)的結(jié)點(diǎn))在原數(shù)組中的索引為 (n-1)/2 -1
  2. 從(n-1)/2-1向前依次執(zhí)行下沉操作谷羞,從而得到堆有序的數(shù)組
堆初始化

堆的排序

  1. 取出堆的根結(jié)點(diǎn),與數(shù)組最后一個(gè)元素交換溜徙。交換后堆有序狀態(tài)可能會(huì)被打破洒宝,需要在新的根結(jié)點(diǎn)進(jìn)行下沉操作,使其恢復(fù)為堆有序狀態(tài)萌京。此時(shí)數(shù)組中最大(大頂堆)/最小(小頂堆)的值存放在數(shù)組末位,除它以外的最 大/小 值位于堆頂宏浩。
  2. 從數(shù)組中排除最后一個(gè)元素知残,重復(fù)步驟2,直到數(shù)組中的元素全部排除時(shí)比庄,完成排序
堆排序
public static void heapSort(Integer arr[]) {
    int n = arr.length;
    //堆的構(gòu)造,對(duì)每一個(gè)含有孩子的結(jié)點(diǎn)做下沉操作,得到大頂堆
    for (int i = (n-1) /2 -1; i >= 0; i--) {
        heapSink(arr, i, n);
    }
    printArr(arr, "大頂堆");
    for (int i = n - 1; i > 0; i--) {
        swap(arr, 0, i);
        heapSink(arr, 0, i);
    }
    printArr(arr, "堆排序");

}

public static void heapSink(Integer arr[], int i, int length) {
    while(2 * k + 1 <= length - 1) {//若k存在子結(jié)點(diǎn)求妹,則進(jìn)入循環(huán)
        int j = 2 * k + 1; //獲取k的第一個(gè)子結(jié)點(diǎn)
        if (j < length - 1 && arr[j] < arr[j + 1]) {//若存在兩個(gè)子結(jié)點(diǎn),則找到其中較大的子結(jié)點(diǎn)
            j++;
        }
        if (arr[j] > arr[k]) {//若k的較大子結(jié)點(diǎn)比k大佳窑,則交換它們的位置
            swap(arr, j, k);
            k = j;
        }
    }
}

堆排序動(dòng)態(tài)圖


堆排序動(dòng)態(tài)圖

小結(jié)

堆排序算法也是一種選擇排序算法制恍,整體由堆的構(gòu)建、堆的交換與下沉兩個(gè)步驟組成神凑。其中堆的構(gòu)建需要比較(n-1)/2-1次下沉净神,每次下沉至多交換一次,時(shí)間復(fù)雜度為O(n)溉委;堆的交換與下沉中需交換n次鹃唯,下沉依次需要執(zhí)行\log_2(n-1),log_2(n-2)...1次交換,近似為Nlog_2N瓣喊。因此堆排序的時(shí)間復(fù)雜度為O(N\log_2N)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坡慌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子藻三,更是在濱河造成了極大的恐慌洪橘,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棵帽,死亡現(xiàn)場(chǎng)離奇詭異熄求,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)逗概,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門抡四,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事指巡∈缏模” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵藻雪,是天一觀的道長(zhǎng)秘噪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)勉耀,這世上最難降的妖魔是什么指煎? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮便斥,結(jié)果婚禮上至壤,老公的妹妹穿的比我還像新娘。我一直安慰自己枢纠,他們只是感情好像街,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著晋渺,像睡著了一般镰绎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上木西,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天畴栖,我揣著相機(jī)與錄音,去河邊找鬼八千。 笑死吗讶,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的恋捆。 我是一名探鬼主播关翎,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼鸠信!你這毒婦竟也來(lái)了纵寝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤星立,失蹤者是張志新(化名)和其女友劉穎爽茴,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绰垂,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡室奏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了劲装。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胧沫。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昌简,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绒怨,到底是詐尸還是另有隱情纯赎,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布南蹂,位于F島的核電站犬金,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏六剥。R本人自食惡果不足惜晚顷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望疗疟。 院中可真熱鬧该默,春花似錦、人聲如沸策彤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锅锨。三九已至,卻和暖如春恋沃,著一層夾襖步出監(jiān)牢的瞬間必搞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工囊咏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恕洲,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓梅割,卻偏偏與公主長(zhǎng)得像霜第,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子户辞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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

  • 一些概念 數(shù)據(jù)結(jié)構(gòu)就是研究數(shù)據(jù)的邏輯結(jié)構(gòu)和物理結(jié)構(gòu)以及它們之間相互關(guān)系泌类,并對(duì)這種結(jié)構(gòu)定義相應(yīng)的運(yùn)算,而且確保經(jīng)過(guò)這...
    Winterfell_Z閱讀 5,774評(píng)論 0 13
  • 1 初級(jí)排序算法 排序算法關(guān)注的主要是重新排列數(shù)組元素底燎,其中每個(gè)元素都有一個(gè)主鍵刃榨。排序算法是將所有元素主鍵按某種方...
    深度沉迷學(xué)習(xí)閱讀 1,404評(píng)論 0 1
  • 第一章 緒論 什么是數(shù)據(jù)結(jié)構(gòu)? 數(shù)據(jù)結(jié)構(gòu)的定義:數(shù)據(jù)結(jié)構(gòu)是相互之間存在一種或多種特定關(guān)系的數(shù)據(jù)元素的集合双仍。 第二章...
    SeanCheney閱讀 5,771評(píng)論 0 19
  • 概述 排序有內(nèi)部排序和外部排序枢希,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大朱沃,一次不能容納全部...
    蟻前閱讀 5,183評(píng)論 0 52
  • 概述:排序有內(nèi)部排序和外部排序苞轿,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序茅诱,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部...
    每天刷兩次牙閱讀 3,730評(píng)論 0 15