排序算法系列(4)——堆排序

堆排序快速排序一樣也是一個O(n logn)的排序算法

但是二者是不一樣實現(xiàn)原理

[這是肯定的,不要pia我]

分類上來看
快速排序 屬于交換排序
堆排序(heap sort)屬于選擇排序

前些日子接到一個電面,上來就問堆排序摊册,我一臉懵逼志于,我真的不記得贵少,我只能一臉懵逼的說我不會
超級尷尬有沒有镰烧!所以為了以后避免這種情況的發(fā)生乔询,先好好學(xué)堆排序吧谎砾。

先說原理:


1. 首先說一下

<u>堆heap</u>:
首先邏輯上的結(jié)構(gòu)是一個完全二叉樹(如果不知道什么是完全二叉樹的可以去百度)
然后按照父節(jié)點(diǎn)比子女節(jié)點(diǎn)都大/都小 分為大根堆和小根堆(也叫大頂堆 和 小頂堆)
大根堆:該完全二叉樹的節(jié)點(diǎn)任意一個非葉子節(jié)點(diǎn)的value都比它的子女節(jié)點(diǎn)大(>=)
小根堆:該完全二叉樹的節(jié)點(diǎn)任意一個非葉子節(jié)點(diǎn)的value都比它的子女節(jié)點(diǎn)蟹瓯丁(<=)

主要到前面的那個邏輯上了么?
因為目前遇到的堆排序其實很少是在樹這個數(shù)據(jù)結(jié)構(gòu)上進(jìn)行的(或者說是真正的在java/c中創(chuàng)建一個TreeNode類或者結(jié)構(gòu)體) 而是用數(shù)據(jù)/list 來存儲節(jié)點(diǎn)的(特指完全二叉樹景图,因為完全二叉樹的結(jié)構(gòu)真的是太完美了)较雕。


哎呀呀,忍不住來說一下一些很簡單的數(shù)組和完全二叉樹的轉(zhuǎn)化

其實就算是當(dāng)成游戲來找找規(guī)律挚币,這個轉(zhuǎn)化也不是很困難亮蒋,其實數(shù)組就是完全二叉樹的水平遍歷出來的。

然后不難發(fā)現(xiàn)在生成的數(shù)組中
array[0]其實是相當(dāng)于樹的根節(jié)點(diǎn)妆毕,然后她的子節(jié)點(diǎn)是array[1]和array[2]
array[1]子節(jié)點(diǎn)是array[3] array[4]
array[2]子節(jié)點(diǎn)是array[5] array[6]
………………………………….....
以此類推
非葉節(jié)點(diǎn)的子節(jié)點(diǎn)在數(shù)組中的體現(xiàn)就是
array[i]的子節(jié)點(diǎn)是 array[2i+1]和array[2i+2];

完全二叉樹和數(shù)組的關(guān)系.png

那么

大根堆就是:array[i] >= array[2i+1] && array[i] >= array[2i+2]
小根堆就是:array[i] <= array[2i+1] && array[i] <= array[2i+2]


算法詳細(xì)解釋

根據(jù)上面的對于堆的描述我們不難發(fā)現(xiàn)慎玖,

如果是一個大頂堆,那么根元素是最大的值
如果是一個小頂堆笛粘,那么跟元素是最小的值

但是堆也不是<u>有序</u>的趁怔,它只能保障節(jié)點(diǎn)的值比它的子女們大/小,但是他們子女們的大小順序是沒有保障的

因此需要通過排序薪前,才能實現(xiàn)堆的排序

2. 正式講堆排序的算法內(nèi)容:


①润努、首先構(gòu)造一個堆,堆排序是建立在堆的基礎(chǔ)上的(無序堆)

②示括、根據(jù)構(gòu)建的堆铺浇,將堆中第一個元素(堆頂/根節(jié)點(diǎn))和最后一個元素交換位置
那么最后一個元素就是一個最值,然后排除這個最后一個元素垛膝,將剩下的完全二叉樹重新調(diào)整為堆(無序堆)鳍侣,重復(fù)步驟②丁稀,直到只剩下一個元素為止

因此,
如果是以大頂堆來構(gòu)建 那么最后得到的是水平遍歷后是從小到大的完全二叉樹(有序小頂堆)
如果是以小頂堆來構(gòu)建 那么最后得到的是水平遍歷后是從大到小的完全二叉樹(有序大頂堆)


現(xiàn)在其實核心的算法并不難理解
而且看到重復(fù)步驟②這種口吻倚聚,我們也不難發(fā)現(xiàn)實現(xiàn)原理就是通過遞歸或者循環(huán)的方式

3. 其次要注意的是:

只有在步驟①的時候才會對初始化的數(shù)組進(jìn)行建堆线衫,而在步驟②的每次循環(huán)中只需要對堆進(jìn)行調(diào)整(adjust)就可以了,畢竟每次只是將根節(jié)點(diǎn)改變秉沼,只需要對根節(jié)點(diǎn)進(jìn)行重新調(diào)整為一個堆就好桶雀,要是還是按照步驟①那樣子建堆的話,會浪費(fèi)很多不必要的邏輯開銷唬复。

[具體的建堆和調(diào)整堆矗积,下面會慢慢講,不要著急敞咧,心急吃不了熱豆腐]


/**
* nums需要排序的堆數(shù)組
**/
public void sort(int[] nums){

    //首先定義出口 當(dāng)只剩一個元素的時候 就可以出去了
       if(start==(end-1)){
            return;
       }
        //對[start,end)建堆
        buildeHeap(nums);

        for(int i = nums.length ; i < nums.length ; i--){
            //交換 start位置和end-1位置上的值
         }
        
        swap()
       sort(nums,start,end-1);
}

public void buildHeap(int [] nums){
    //coding;
};

那么接下來比較重點(diǎn)的就是如何構(gòu)建一個最大堆/最小堆以及堆的調(diào)整

按照堆排序的思路完整流程走一遍:

建立堆棘捣,首先將數(shù)組直接按照水平遍歷的方式建立一個樹:[如下例]
數(shù)組int [] nums如下圖:

array.png

這個數(shù)組先建立一個完全二叉樹,如下

c-tree.png

然后開始倒著遍歷非葉節(jié)點(diǎn):

根據(jù)數(shù)組和樹之間的映射關(guān)系休建,不難發(fā)現(xiàn)乍恐,最后一個非葉節(jié)點(diǎn)是14那個節(jié)點(diǎn),數(shù)數(shù)在數(shù)組中的index是不是4测砂? 數(shù)組的長度length是不是11茵烈?

然后找規(guī)律:

不難發(fā)現(xiàn) 完全二叉樹中(完全二叉樹,看清限制條件)
非葉節(jié)點(diǎn)的個數(shù) = length/2
葉節(jié)點(diǎn)的個數(shù) = (length+1)/2
最后一個非葉節(jié)點(diǎn)在對應(yīng)數(shù)組中的index = length/2 - 1
【后來樓主試了一下砌些,前兩條性質(zhì)符合所有的二叉樹
【樓主自認(rèn)沒啥大本事呜投,這種規(guī)律記住就好了,以后沒準(zhǔn)會用這個規(guī)律去推其他規(guī)律存璃,但是這個規(guī)律我是真的不知道咋推出來的了仑荐。。纵东。ORZ 有大神可以告知一下】


以構(gòu)建大根堆為例子:
第一個非葉節(jié)點(diǎn): 以及調(diào)整后的情形

1.png

第二個

2.png

第三個[由于滿足條件 所以不需要交換]

3.png

4.png

第四個 [換了之后發(fā)現(xiàn)粘招,5換后的位置依然不滿足要求需要再接著和下層換]
第五個 也就是最后一個:

5.png

這樣就構(gòu)造出來了一個最大堆,我們不難看出來偎球,其實堆并不是有序的洒扎,你水平遍歷一下就知道,這是無序的衰絮,因此這是一個無序堆逊笆,但是不論什么堆,只要是堆岂傲,那么他的根節(jié)點(diǎn),絕對是一個最值(如本例是最大堆所以頂部就是一個最大值)

然后開始排序吧
首先將根元素和尾元素swap(互換一下)

6.png

如圖這樣子就相當(dāng)于排好一個元素了[20]

然后默認(rèn)從數(shù)組中刪除最后一個元素[去掉排好序的元素] [對于圖中來說不考慮藍(lán)色的節(jié)點(diǎn)]

對剩余節(jié)點(diǎn)進(jìn)行調(diào)整為堆子檀,但是我們不難發(fā)現(xiàn)镊掖,由于其他元素在構(gòu)建堆的時候已經(jīng)滿足堆的需求的了所以沒必要從最后一個非葉子節(jié)點(diǎn)處進(jìn)行重構(gòu)堆乃戈,只需要對第一個元素[5] (剛才互換的節(jié)點(diǎn)) 進(jìn)行調(diào)整使其滿足堆的性質(zhì)就會又構(gòu)建成一個堆,因此不難發(fā)現(xiàn)亩进,調(diào)整比構(gòu)建堆簡單得很症虑。

7.png

然后再把第一個元素和最后一個元素[邏輯概念上就是 19 和 5 , 20還是存在的,只不是過我剛才說過就是你就當(dāng)它不存在归薛,藍(lán)色都是不存在的谍憔,那么第一個節(jié)點(diǎn)是19 第二個節(jié)點(diǎn)是5 互換了之后如下]

8.png

這樣就相當(dāng)于排好兩個元素了,再對剩下的元素(除了藍(lán)色的節(jié)點(diǎn)) 如法炮制 -> 調(diào)整堆 -> 互換位置 -> 調(diào)整堆 -> 互換位置 ………………直到只剩一個元素為止主籍,那么也沒有調(diào)整的必要了 233333333


<font color=blue>終于講完了算法了习贫,自己也通了一遍,可以開開心心地寫代碼了</font>
擼主講的思路都這么清楚了千元,代碼就一帶而過好了苫昌,搞懂思路,代碼也就比較容易寫了幸海。


package HeapSort;

import java.util.Arrays;

import javax.xml.transform.Templates;

/**
 * 主要是構(gòu)建最大堆
 * 最后生成從小到大排序的序列
 * 算法復(fù)雜度為O(N*logN)
 * @author mengf
 *
 */
public class HeapSort {
    
    public static void main(String[] args) {
        HeapSort sort = new HeapSort();
        int[] nums = {12,5,17,9,14,13,2,4,19,8,20};
        sort.heapSort(nums);
        System.out.println(Arrays.toString(nums));
    }
    
    /**
     * 構(gòu)建最大堆
     * @param nums
     */
    public void heapSort(int[] nums){
        if (nums.length<=1) {
            //數(shù)量<=1 的數(shù)組排序的意義在哪祟身? exo me? 
            //你的良心不會痛么N锒馈M嗔颉!挡篓!
            return;
        }
        buildHeap(nums);
        //其實都知道調(diào)整的次數(shù)為多少的
        //i 其實就是swap后剩下的節(jié)點(diǎn)的個數(shù) 也就是length
        for(int i = nums.length-1 ; i >= 1 ; i--){
            //System.out.println("到這里了");
            //swap
            swap(nums, i, 0);
            //adjust 
            adjustHeap(nums, 0 ,i);
        }
    }
    
    private void buildHeap(int[] nums) {
        //創(chuàng)建最大堆
        
        int length = nums.length;
        
        //index = n/2 -1 ;
        int index = length/2 -1 ;
        //最后一個非葉節(jié)點(diǎn)的index
        
        //調(diào)整為最大堆
        for(int i = index ; i >= 0 ;  i--){
            adjustHeap(nums, i, length);
        }
    }
    
    /**
     * 
     * @param nums 表示這個數(shù)組
     * @param i 表示想要調(diào)整的對應(yīng)的位置
     * @param length 表示這個堆對應(yīng)數(shù)組的長度 注意不一定是nums的長度哦 婉陷,可能是剩余沒排好序的長度
     */
    private void adjustHeap(int[] nums,int i,int length){
        int left = 2 * i + 1;
        int right = 2 * i + 2;
        int temp = i;
        int tempMax = nums[i];
        
        while (true) {
            //System.out.println(temp);
            if (right < length && tempMax < nums[right]) {
                tempMax = nums[right];
                temp = right;
            }
            
            if (left < length && tempMax < nums[left]) {
                tempMax = nums[left];
                temp = left;
            }
            
            //如果最大的位置還是i的話 那就不用換了
            if (temp == i) {
                break;
            }else {
                swap(nums, temp, i);
                //交換完 后 重新初始化 right left 以及默認(rèn)的位置為i 默認(rèn)最大值為nums[temp]
                left = 2* temp + 1;
                right = 2 * temp +2;
                i = temp;
                tempMax = nums[temp];
            } 
            
        }
    }
    
    private void  swap(int nums[], int i,int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

琢磨了好久,都去參考了一下網(wǎng)上的代碼才寫出來的瞻凤,看來還是不行憨攒,需要多練,懂了原理是一碼事情阀参,會踐行到代碼中又是一件事情肝集,看來要每天寫一發(fā)了!蛛壳!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杏瞻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子衙荐,更是在濱河造成了極大的恐慌捞挥,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忧吟,死亡現(xiàn)場離奇詭異砌函,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門讹俊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來垦沉,“玉大人,你說我怎么就攤上這事仍劈〔薇叮” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵贩疙,是天一觀的道長讹弯。 經(jīng)常有香客問我,道長这溅,這世上最難降的妖魔是什么组民? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮芍躏,結(jié)果婚禮上邪乍,老公的妹妹穿的比我還像新娘。我一直安慰自己对竣,他們只是感情好庇楞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著否纬,像睡著了一般吕晌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上临燃,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天睛驳,我揣著相機(jī)與錄音,去河邊找鬼膜廊。 笑死乏沸,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的爪瓜。 我是一名探鬼主播蹬跃,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼铆铆!你這毒婦竟也來了蝶缀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤薄货,失蹤者是張志新(化名)和其女友劉穎翁都,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谅猾,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡柄慰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年鳍悠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片先煎。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡贼涩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出薯蝎,到底是詐尸還是另有隱情,我是刑警寧澤谤绳,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布占锯,位于F島的核電站,受9級特大地震影響缩筛,放射性物質(zhì)發(fā)生泄漏消略。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一瞎抛、第九天 我趴在偏房一處隱蔽的房頂上張望艺演。 院中可真熱鬧,春花似錦桐臊、人聲如沸胎撤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伤提。三九已至,卻和暖如春认烁,著一層夾襖步出監(jiān)牢的瞬間肿男,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工却嗡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舶沛,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓窗价,卻偏偏與公主長得像如庭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子舌镶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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

  • 堆排序可以做什么 首先應(yīng)該弄清楚堆排序可以解決什么問題柱彻,答案是顯而易見的:排序。說得通俗點(diǎn)兒就是對一組無序的數(shù)字進(jìn)...
    Springlord888閱讀 30,362評論 11 52
  • 1 序 2016年6月25日夜餐胀,帝都哟楷,天下著大雨,拖著行李箱和同學(xué)在校門口照了最后一張合照否灾,搬離寢室打車去了提前租...
    RichardJieChen閱讀 5,096評論 0 12
  • 樹的概述 樹是一種非常常用的數(shù)據(jù)結(jié)構(gòu)卖擅,樹與前面介紹的線性表,棧,隊列等線性結(jié)構(gòu)不同惩阶,樹是一種非線性結(jié)構(gòu) 1.樹的定...
    Jack921閱讀 4,448評論 1 31
  • 兩片腳丫 泡成熱帶魚 電腦主機(jī)嗚嗚轟鳴 黑夜掩埋喧囂 老貓小貓剛結(jié)束一場打斗 歪著頭睡在爪子上 沙發(fā)一動不動 疼痛...
    羅什蓮花閱讀 115評論 0 1
  • 人不怕不知足挎狸,只是怕活在愚蠢的知足里而不自知。 我是今年才很多次聽到自律這個詞并且正視它的断楷,自律锨匆,一個含義太豐富的...
    浪漫的高貴閱讀 1,072評論 2 4