具體把握堆排序(JAVA)

前言


??堆排序是一種動(dòng)態(tài)排序舷丹,它基于堆這種數(shù)據(jù)結(jié)構(gòu)。堆的實(shí)質(zhì)是一棵二叉樹县忌,只不過使用的是連續(xù)存儲(chǔ)掂榔。堆分為小根堆和大根堆继效。小根堆的特點(diǎn)是根結(jié)點(diǎn)最小,其每一個(gè)子堆也滿足這個(gè)特點(diǎn)装获。相反瑞信,大根堆的特點(diǎn)是根結(jié)點(diǎn)最大,且其每一個(gè)子堆也滿足這一個(gè)特點(diǎn)穴豫。

算法


??堆排序的實(shí)質(zhì)是堆頂元素出堆凡简,而后再維護(hù)堆的特點(diǎn)進(jìn)行調(diào)整,繼續(xù)出堆直至堆為空精肃,排序完成秤涩。可按如下步驟進(jìn)行:
??1. 初始建堆司抱。根據(jù)序列構(gòu)造完全二叉樹筐眷,并進(jìn)行調(diào)整,以滿足堆的特點(diǎn)习柠,由于是升序排序匀谣,所以建立小根堆。因?yàn)槊恳粋€(gè)子堆也必須滿足小根堆的特點(diǎn)资溃,所以需要對(duì)結(jié)點(diǎn)自底向上進(jìn)行調(diào)整武翎。葉子結(jié)點(diǎn)顯然已滿足,所以直接從最后一個(gè)葉子結(jié)點(diǎn)的父節(jié)點(diǎn)開始調(diào)整溶锭,也就是從序號(hào)為n/2的位置開始調(diào)整到根結(jié)點(diǎn)位置(位置為1)結(jié)束宝恶。
??對(duì)于每一個(gè)子堆的調(diào)整,其實(shí)質(zhì)是可能存在孩子結(jié)點(diǎn)小于根結(jié)點(diǎn)趴捅,因而需要作向下調(diào)整垫毙。調(diào)整的方法是先保存根結(jié)點(diǎn),再取其孩子結(jié)點(diǎn)(如果存在兩個(gè)孩子結(jié)點(diǎn)則取其最小的一個(gè))驻售,若這個(gè)結(jié)點(diǎn)值大于根結(jié)點(diǎn)值露久,則沒有調(diào)整的必要,調(diào)整結(jié)束欺栗。否則毫痕,將這個(gè)結(jié)點(diǎn)值賦值給根結(jié)點(diǎn),位置也賦值給根結(jié)點(diǎn)的位置迟几,繼續(xù)向下一層的孩子結(jié)點(diǎn)進(jìn)行比較消请,直到調(diào)整到葉結(jié)點(diǎn)或者已經(jīng)滿足特性。
??2. 堆頂元素出堆类腮。出堆的過程是將堆頂元素和堆底元素交換臊泰,并且堆長(zhǎng)度減1。由于交換后蚜枢,堆特性可能已經(jīng)被破壞缸逃,因此需要從新調(diào)整根結(jié)點(diǎn)的位置针饥,重新向下調(diào)整。出堆操作不斷進(jìn)行下去需频,直至堆為空丁眼,排序結(jié)束。輸出出堆的元素昭殉,即為升序序列苞七。

例子


仍然以序列4,3,1,2,5為例:
1.初始建堆,堆長(zhǎng)為5

??初始二叉樹為:
初始二叉樹

??結(jié)點(diǎn)3向下調(diào)整挪丢。由于下層孩子結(jié)點(diǎn)2小于3蹂风,故2和3交換:
結(jié)點(diǎn)3調(diào)整

調(diào)整后序號(hào)2為根結(jié)點(diǎn)的子堆已滿足小根堆特點(diǎn)。

??結(jié)點(diǎn)4向下調(diào)整乾蓬。由于下層孩子結(jié)點(diǎn)惠啄,最小的是結(jié)點(diǎn)1,則將1和4交換:
結(jié)點(diǎn)4向下調(diào)整

可見此時(shí)已完全滿足小根堆特點(diǎn)巢块。
  1. 堆頂元素出堆礁阁。

    ??首先輸出1,且將堆頂堆底交換族奢,即1和5交換。堆長(zhǎng)為4:
    1出堆

    結(jié)點(diǎn)5向下調(diào)整后變?yōu)椋?div id="mn775bd" class="image-package">
    5向下調(diào)整

??再輸出2丹鸿,將2和5交換越走,堆長(zhǎng)為3。再將結(jié)點(diǎn)5向下調(diào)整后為:
2出堆

??再輸出3靠欢,將3和5交換廊敌,堆長(zhǎng)為2。再將結(jié)點(diǎn)5向下調(diào)整后為:
3出堆

??再輸出4门怪,將4和5交換骡澈,堆長(zhǎng)為1。無須調(diào)整:
4出堆

??再輸出5掷空,堆長(zhǎng)為0肋殴,堆為空,排序結(jié)束:
5出堆

??排序后序列為1,2,3,4,5坦弟。排序成功护锤。

Codes


package com.fairy.InnerSort;

import java.util.Scanner;
/**
 * 定義堆數(shù)據(jù)結(jié)構(gòu)
 * @author Fairy2016
 *
 */
class Heap {
    int data[];//元素
    int length;//堆長(zhǎng)
    int max = 100;//堆空間大小,如不夠每次再加
    //初始建堆
    void InitBuildHeap(int a[], int n) {
        int i;
        data = new int[n+1];
        length = n;
        for(i = 1; i <= n; i++) {
            data[i] = a[i];
        }
        //從最后一個(gè)結(jié)點(diǎn)的父節(jié)點(diǎn)開始調(diào)整
        for(i = n/2; i > 0; i--) {
            AdjustDown(i);
        }
    }
    //向下調(diào)整酿傍,以保持堆的特性以及子堆的特性
    void AdjustDown(int k) {
        data[0] = data[k];
        for(int i = 2*k; i <= length; i*=2) {
            if(i < length && data[i] > data[i+1])
                i++;//由于一個(gè)結(jié)點(diǎn)的子節(jié)點(diǎn)可能有兩個(gè)烙懦,還需比較大小
            if(data[0] < data[i]) {
                break;//如果其子節(jié)點(diǎn)都比它大,那么無須調(diào)整
            } else {
                data[k] = data[i];
                k = i;//向上賦值赤炒,更新結(jié)點(diǎn)位置
            }
        }
        data[k] = data[0];//賦值到調(diào)整后確定的位置
    }
    //向上調(diào)整氯析,用于新入堆元素后保持堆的特性
    void AdjustUp(int k) {
        data[0] = data[k];
        for(int i = k/2; i > 0; i /= 2) {
            if(data[0] > data[i]) {
                break;//如果父結(jié)點(diǎn)都比它小亏较,那么無須調(diào)整
            } else {
                data[k] = data[i];
                k = i;//向下賦值,更新結(jié)點(diǎn)位置
            }
        }
        data[k] = data[0];
    }
    //元素出堆
    int PopupHeap() {
        int e = data[1];
        //交換堆頂和堆底元素
        data[1] = data[length];
        data[length] = e;
        //堆長(zhǎng)度減1
        length--;
        //需要調(diào)整以滿足堆特性
        AdjustDown(1);
        return e;
    }
    //判斷堆是否已空
    boolean IsEmpty() {
        if(length >= 1) {
            return false;
        }
        return true;
    }
    
    //元素入堆
    void PushHeap(int e) {
        //如果空間不夠掩缓,需從新分配
        if(length == data.length-1) {
            int capa = max;
            while(capa <= length+1) {
                capa += max;
            }
            int help[] = new int[length];
            for(int i = 1; i <= length; i++) {
                 help[i-1] = data[i];
            }
            data = new int[capa];
            for(int i = 1; i <= length; i++) {
                 data[i] = help[i-1];
            }
            data[++length] = e;//新元素加入到堆底
        } else {
            data[++length] = e;//新元素加入到堆底
        }
        AdjustUp(length);
    }
}
/**
 * 堆排序
 * @author Fairy2016
 *
 */
public class HeapSort {
    
    public static void sort(int a[], int n) {
        Heap heap = new Heap();
        heap.InitBuildHeap(a, n);
        while(!heap.IsEmpty()) {
            System.out.print(heap.PopupHeap()+" ");
        }
    }
    
    public static void main(String args[]) {
        int n;
        int a[];
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext()) {
            n = scanner.nextInt();
            if(n > 0) {
                a = new int[n+1];
                for(int i=1; i <= n; i++) {
                    a[i] = scanner.nextInt();
                }
                sort(a, n);
            }
        }
        scanner.close();
    }
}

后記


??堆排序的時(shí)間復(fù)雜度由出堆操作和調(diào)整維護(hù)堆特性操作嵌套決定宴杀,出堆操作o(n),調(diào)整操作o(log2(n))拾因。所以時(shí)間復(fù)雜度為o(n*log2(n))旺罢。時(shí)間復(fù)雜度還算可以,但由于其用到了堆這個(gè)數(shù)據(jù)結(jié)構(gòu)绢记,空間復(fù)雜度為o(n)扁达,相對(duì)來說不如快速排序。
??堆的應(yīng)用遠(yuǎn)遠(yuǎn)不止堆排序蠢熄,而是作為一種重要的數(shù)據(jù)結(jié)構(gòu)(優(yōu)先隊(duì)列)應(yīng)用在實(shí)際開發(fā)中跪解。本文雖然是簡(jiǎn)單的堆排序,但代碼中對(duì)于堆這種數(shù)據(jù)結(jié)構(gòu)還是進(jìn)行了封裝签孔,包括其插入元素操作叉讥。其實(shí)無論是入堆還是出堆,元素操作之后還是要維護(hù)堆的特性不變饥追,即大根堆仍然是大根堆图仓,小根堆仍然是小根堆。更多關(guān)于堆的相關(guān)應(yīng)用但绕,在后面的貪心算法和分支限界算法文章會(huì)繼續(xù)提到救崔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市捏顺,隨后出現(xiàn)的幾起案子六孵,更是在濱河造成了極大的恐慌,老刑警劉巖幅骄,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件劫窒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拆座,警方通過查閱死者的電腦和手機(jī)主巍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來懂拾,“玉大人煤禽,你說我怎么就攤上這事♂常” “怎么了檬果?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我选脊,道長(zhǎng)杭抠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任恳啥,我火速辦了婚禮偏灿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钝的。我一直安慰自己翁垂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布硝桩。 她就那樣靜靜地躺著沿猜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碗脊。 梳的紋絲不亂的頭發(fā)上啼肩,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音衙伶,去河邊找鬼祈坠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛矢劲,可吹牛的內(nèi)容都是我干的赦拘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼卧须,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼另绩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起花嘶,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蹦漠,沒想到半個(gè)月后椭员,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笛园,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年隘击,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片研铆。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡埋同,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出棵红,到底是詐尸還是另有隱情凶赁,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站虱肄,受9級(jí)特大地震影響致板,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咏窿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一斟或、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧集嵌,春花似錦萝挤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至咽块,卻和暖如春绘面,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背侈沪。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工揭璃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人亭罪。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓瘦馍,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親应役。 傳聞我的和親對(duì)象是個(gè)殘疾皇子情组,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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

  • 概述排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序箩祥,而外部排序是因排序的數(shù)據(jù)很大院崇,一次不能容納全部的...
    Luc_閱讀 2,270評(píng)論 0 35
  • 概述 排序有內(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
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 將一個(gè)記錄插入到已排序好...
    依依玖玥閱讀 1,253評(píng)論 0 2
  • 排序的基本概念 在計(jì)算機(jī)程序開發(fā)過程中,經(jīng)常需要一組數(shù)據(jù)元素(或記錄)按某個(gè)關(guān)鍵字進(jìn)行排序凳鬓,排序完成的序列可用于快...
    Jack921閱讀 1,428評(píng)論 1 4