詳解堆排序

2021-02-28 算法愛好者
(點擊上方公眾號,可快速關注)

轉自:靜默虛空

http://www.cnblogs.com/jingmoxukong/p/4303826.html

好文投稿, 請點擊 → 這裡了解詳情

堆的概念

堆是一棵順序存儲的完全二叉樹。

其中每個結點的關鍵字都不大於其孩子結點的關鍵字,這樣的堆稱為小根堆。

其中每個結點的關鍵字都不小於其孩子結點的關鍵字,這樣的堆稱為大根堆。

舉例來說,對於n個元素的序列{R0, R1, ... , Rn}若且唯若滿足下列關係之一時,稱之為堆:

(1) Ri <= R2i+1 且 Ri <= R2i+2 (小根堆)

(2) Ri >= R2i+1 且 Ri >= R2i+2 (大根堆)

其中i=1,2,…,n/2向下取整; 

如上圖所示,序列R{3, 8, 15, 31, 25}是一個典型的小根堆。

堆中有兩個父結點,元素3和元素8。

元素3在數組中以R[0]表示,它的左孩子結點是R[1],右孩子結點是R[2]。

元素8在數組中以R[1]表示,它的左孩子結點是R[3],右孩子結點是R[4],它的父結點是R[0]。可以看出,它們滿足以下規律:

設當前元素在數組中以R[i]表示,那麼,

(1) 它的左孩子結點是:R[2*i+1];

(2) 它的右孩子結點是:R[2*i+2];

(3) 它的父結點是:R[(i-1)/2];

(4) R[i] <= R[2*i+1] 且 R[i] <= R[2i+2]。

要點


首先,按堆的定義將數組R[0..n]調整為堆(這個過程稱為創建初始堆),交換R[0]和R[n];然後,將R[0..n-1]調整為堆,交換R[0]和R[n-1];

如此反覆,直到交換了R[0]和R[1]為止。 

以上思想可歸納為兩個操作:

(1)根據初始數組去構造初始堆(構建一個完全二叉樹,保證所有的父結點都比它的孩子結點數值大)。

(2)每次交換第一個和最後一個元素,輸出最後一個元素(最大值),然後把剩下元素重新調整為大根堆。 

當輸出完最後一個元素後,這個數組已經是按照從小到大的順序排列了。

先通過詳細的實例圖來看一下,如何構建初始堆。

設有一個無序序列 { 1, 3, 4, 5, 2, 6, 9, 7, 8, 0 }。

構造了初始堆後,我們來看一下完整的堆排序處理:

還是針對前面提到的無序序列 { 1, 3, 4, 5, 2, 6, 9, 7, 8, 0 } 來加以說明。

相信,通過以上兩幅圖,應該能很直觀的演示堆排序的操作處理。 

核心代碼


public void HeapAdjust(int[ ] array, int parent, int length) {

    int temp = array[parent]; // temp保存當前父節點

    int child = 2 * parent + 1; // 先獲得左孩子 

    while (child < length) {

        // 如果有右孩子結點,並且右孩子結點的值大於左孩子結點,則選取右孩子結點

        if (child + 1 < length && array[child] < array[child + 1]) {

            child++;

        }

        // 如果父結點的值已經大於孩子結點的值,則直接結束

        if (temp >= array[child])

            break;

        // 把孩子結點的值賦給父結點

        array[parent] = array[child];

        // 選取孩子結點的左孩子結點,繼續向下篩選

        parent = child;

        child = 2 * child + 1;

    }

    array[parent] = temp;

}

public void heapSort(int[] list) {

    // 循環建立初始堆

    for (int i = list.length / 2; i >= 0; i--) {

        HeapAdjust(list, i, list.length);

    } 

    // 進行n-1次循環,完成排序

    for (int i = list.length - 1; i > 0; i--) {

        // 最後一個元素和第一元素進行交換

        int temp = list[i];

        list[i] = list[0];

        list[0] = temp;

        // 篩選 R[0] 結點,得到i-1個結點的堆

        HeapAdjust(list, 0, i);

        System.out.format("第 %d 趟: \t", list.length - i);

        printPart(list, 0, list.length - 1);

    }

}

時間複雜度

堆的存儲表示是順序的。因為堆所對應的二叉樹為完全二叉樹,而完全二叉樹通常採用順序存儲方式。

當想得到一個序列中第k個最小的元素之前的部分排序序列,最好採用堆排序。

因為堆排序的時間複雜度是O(n+klog2n),若k≤n/log2n,則可得到的時間複雜度為O(n)。

算法穩定性

堆排序是一種不穩定的排序方法。

因為在堆的調整過程中,關鍵字進行比較和交換所走的是該結點到葉子結點的一條路徑,

因此對於相同的關鍵字就可能出現排在後面的關鍵字被交換到前面來的情況。 

完整參考代碼

public class HeapSort {

    public void HeapAdjust(int[] array, int parent, int length) {

        int temp = array[parent]; // temp保存當前父節點

        int child = 2 * parent + 1; // 先獲得左孩子

        while (child < length) {

            // 如果有右孩子結點,並且右孩子結點的值大於左孩子結點,則選取右孩子結點

            if (child + 1 < length && array[child] < array[child + 1]) {

                child++;

            }

            // 如果父結點的值已經大於孩子結點的值,則直接結束

            if (temp >= array[child])

                break;

            // 把孩子結點的值賦給父結點

            array[parent] = array[child];

            // 選取孩子結點的左孩子結點,繼續向下篩選

            parent = child;

            child = 2 * child + 1;

        }

        array[parent] = temp;

    }

    public void heapSort(int[] list) {

        // 循環建立初始堆

        for (int i = list.length / 2; i >= 0; i--) {

            HeapAdjust(list, i, list.length);

        }

        // 進行n-1次循環,完成排序

        for (int i = list.length - 1; i > 0; i--) {

            // 最後一個元素和第一元素進行交換

            int temp = list[i];

            list[i] = list[0];

            list[0] = temp;

            // 篩選 R[0] 結點,得到i-1個結點的堆

            HeapAdjust(list, 0, i);

            System.out.format("第 %d 趟: \t", list.length - i);

            printPart(list, 0, list.length - 1);

        }

    }

    // 列印序列

    public void printPart(int[] list, int begin, int end) {

        for (int i = 0; i < begin; i++) {

            System.out.print("\t");

        }

        for (int i = begin; i <= end; i++) {

            System.out.print(list[i] + "\t");

        }

        System.out.println();

    }

    public static void main(String[] args) {

        // 初始化一個序列

        int[] array = {

                1, 3, 4, 5, 2, 6, 9, 7, 8, 0

        };

        // 調用快速排序方法

        HeapSort heap = new HeapSort();

        System.out.print("排序前:\t");

        heap.printPart(array, 0, array.length - 1);

        heap.heapSort(array);

        System.out.print("排序後:\t");

        heap.printPart(array, 0, array.length - 1);

    }

}

執行結果:

參考資料


《數據結構習題與解析》(B級第3版)

覺得本文有幫助?請分享給更多人

關注「算法愛好者」,修煉編程內功

淘口令:複製以下紅色內容,再打開手淘即可購買

範品社,使用¥極客T恤¥搶先預覽(長按複製整段文案,打開手機淘寶即可進入活動內容)

相關焦點

  • JavaScript數組 - 引用詳解
    基本數據類型詳解在學習數組引用詳解前,我們先來看基本數據類型的詳解舉個小例子:我們聲明一個a = 10;然後聲明一個函數,這個函數裡面有個參數為a把這個參數的a改成5,a = 5; 並且再加上alert(a);函數外我們先去alert(a);再調用這個函數把a寫在裡面傳進去
  • 怎麼樣更好地理解排序算法
    (相等元素的前後順序排序後不變);02選擇排序選擇排序的基本思想就是:每次從未排序的列表中找到最小(大)的元素,放到已排序序列的末尾,直到所有元素排序完畢。04希爾排序希爾排序也稱為增量遞減排序,是對直接插入算法的改進,基於以下兩點性質:插入排序在對幾乎已排好序的數據操作時,效率高,可以達到線性排序的效率但插入排序一般來說是低效的,因為插入排序每次只能移動一位數據希爾排序的改進是,使用一個增量將數組切分不同的分組,然後在組內進行插入排序,遞減增量,直到增量為 1,好處就是數據能跨多個元素移動,一次比較就可能消除多個元素的交換
  • Excel 數據排序,用函數會嗎?會幾個?
    數據統計中,排序是常見的需求。今天教大家三個可以進行排序的函數。案例:將下圖 1 中的分數分別按以下需求提取出來:從大到小排序從小到大排序計算每個分數對應的排名效果如下圖從大到小排:在 E2 單元格中輸入以下公式 --> 下拉複製公式:=LARGE($C$2:$C$15,ROW(A1))Large 函數詳解作用:
  • Sort an Array 數組排序
    Output: [1,2,3,5]Example 2:Input: [5,1,1,2,0,0]Output: [0,0,1,1,2,5]Note:1<=A.length<=10000-50000<=A[i]<=50000這道題讓我們給數組排序
  • Python,Numpy,Pandas……數據科學家必備排序技巧
    例如,sort(key = len)將按照每個列表項的長度排序。Vanilla Python中唯一使用的排序算法是Timsort。Timsort會根據要排序的數據特徵選擇排序方法。舉個例子,如果排短列表,就採用插入排序。Timsort以及Vanilla Python的其他算法都很穩定。這意味著如果有多個相同值,這些數據在排序後仍維持原始順序。
  • 你相信嗎,排序算法難倒過計算機科學家
    排序是計算機的核心內容。不誇張的說,沒有排序,計算機就不會變成現實。1880年的一天,美國人口普查局的辦公室裡,一名叫赫爾曼·霍爾瑞斯的20歲年輕人正盯著那堆小山般的人口登記冊發呆。 從計算機誕生直到20世紀,排序一直是推動計算機發展的一大動力。為「存儲程序」計算機編寫的第一個代碼就是一個排序程序。20世紀60年代,據評估,世界上超過1/4的計算資源用於排序工作。無論是查找最大值或者最小值,最常見數據或者最罕見數據,還是索引、標記副本、或者根據要求進行查找,其核心工作都是排序。
  • 29 | 堆的應用:如何快速獲取到Top 10的搜索關鍵詞?
    這個問題就可以用堆來解決,這也是堆這種數據結構一個非常典型的應用。上一節我們講了堆和堆排序的一些理論知識,今天我們就來講一講,堆這種數據結構幾個非常重要的應用:優先級隊列、求 Top K 和求中位數。堆的應用一:優先級隊列首先,我們來看第一個應用場景:優先級隊列。
  • excel排序技巧:排序功能應用匯總
    小小的排序,也有很大的學問,之前經常遇到學員來問小編排序的問題,乾脆今天就給大家匯總一下,一次性搞明白~1、普通排序選中表格中任意單元格,點擊「數據」選項卡下的排序和篩選組中的「升序」、「降序」,即可直接完成排序。這時的排序結果是以選中單元格所在列,按漢語拼音首字母順序排列的。
  • Excel排序的潛規則
    上期小爬教了大家Excel的基本排序功能,今天就為大家分享一下排序的潛規則;相信很多小夥伴子工作的時候也經常遇到過類似的問題,那就是我們平時的排序都是按照列來進行排序,如果需要按行、按顏色排序又該如何操作呢?下面小爬就來為大家講解如何操作。
  • JavaScript實現排序算法
    如4N^2^,大O表示法表示為:O(N^2^);二、排序算法這裡主要介紹幾種簡單排序和高級排序:簡單排序:冒泡排序、選擇排序、插入排序;高級排序:希爾排序、快速排序;此處創建一個列表類ArrayList並添加一些屬性和方法,用於存放這些排序方法:1.冒泡排序冒泡排序的思路:對未排序的各元素從頭到尾依次比較相鄰的兩個元素大小關係;如果左邊的人員高
  • 泡泡色彩排序
    泡泡色彩排序極其簡單的玩法讓這款遊戲深受玩家們的歡迎,遊戲操作起來特別的簡單,但也十分的燒腦,遊戲中有著各種各樣不同的挑戰,你需要充分發揮自己的聰明智慧才有可能順利通關,玩起來很是令人放鬆,是一款非常不錯的減壓解悶遊戲。
  • excel中排序的使用方法
    這一章我們來了解下排序的使用方法,Excel中排序的使用方法非常的簡單,重點掌握自定義排序的使用方法首選來介紹下Excel中排序的規則排序的一般規則有3種根據筆劃順序進行排序會根據單元格文字筆畫的多少進行排序我們可以在Excel中的開始功能組中找到排序以及自定義排序升序排列:從小到大的進行排列,數據越來越大降序排列:從大到小的進行排序,數據越來越小很對時候我們選擇某一部分進行排序的話會跳出如下圖所示的對話框
  • python+=和排序
    #不管是list.sort 方法還是sorted 函數,都有兩個可選的關鍵字參數:reverse如果被設定為True,被排序的序列裡的元素會以降序輸出(也就是說把最大值當作最小值來排序)。這個參數的默認值是False;key一個只有一個參數的函數,這個函數會被用在序列裡的每一個元素上,所產生的結果將是排序算法依賴的對比關鍵字。
  • 使用vlookup解決自定義排序的問題,原來自定義排序竟如此簡單
    Hello,大家好,今天跟大家分享下如何自定義排序,實現想怎麼排序就怎麼排序,工作中我們可能會遇到這樣的問題,就是要根據給定的數據位置進行排序,如果我們直接使用排序excel會根據默認的排序規則進行排序,而不能達到我們想要的結果,解決這樣的問題,跟大家分享2種方法,一種是使用自定義排序,一種是使用
  • 圖解:「歸併排序」
    今天小浩給大家分享一篇關於歸併排序的文章。考察歸併排序的題目可以形態各異,但是萬變不離其宗,希望看完今日之章,你能掌握歸併排序及其思想大成。歸併排序 歸併排序和之前的冒泡排序、插入排序和選擇排序不同,其蘊含了一種分治的思想,在本文中我們會詳細說明。
  • D03 Numpy排序、篩選、統計
    title: D03|Numpy排序、篩選、統計author: Adolph Leecategories: 數據挖掘基礎tags:Python數據挖掘基礎統計篩選排序在進行數據挖掘工作之前,我們常常需要對數據的全貌今昔概覽,利用描述性統計獲取數據的特徵,例如數據的均值
  • 關於快速排序
    對於排序,我就學了冒泡排序,就沒管其他的了,而學霸同學那個時候就學會了,這個也許就是我沒有成為學霸的重要原因。高中三年沒碰計算機競賽,因為那時候自作聰明的我,覺得計算機這種大熱必定會大冷,搞點數學物理這種基礎學科才是要緊的,當然數學物理我也不咋地,哈哈哈。說來也搞笑,大學我雖然是搞ACM和數學建模的,其實快排怎麼排的我也沒去管,要比賽了臨時抱抱佛腳,ACM也就靠簡單題做的快混了3年。
  • 圖解:什麼是拓撲排序?
    在正式介紹拓撲排序之前,我們一起看一看必備基礎。拓撲排序基礎篇 總覺得書上的概念有點欠缺生動,但還是需要這些基礎的概念作為支撐。一個 無環的有向圖 稱為 有向無環圖(Directed Acycline Graph),簡稱 DAG 圖。(這不等於沒說嗎?)所以直接看圖。
  • 上帝之城:監獄帝國銷售交易買賣系統詳解
    導 讀 今天小編為大家帶來上帝之城:監獄帝國銷售交易買賣系統詳解: 銷售頁面(v) 1:6類可交易物資,不在這6類的可以去當鋪或者賣給黑市商人。
  • 兩個Excel表格,內容部分重合,排序不同,如何實現排序相同
    可有些朋友不會用VLOOKUP,或者用起來不順手,總是遇到各種錯誤,因此,本文將介紹如何使用排序來處理。貨品不相同的兩表調整順序,比較複雜,先來看看怎樣將貨品相同的兩個表格調整成一樣的順序吧。貨品相同的兩表調整順序方法:複製左表的貨品名稱到記事本中,然後選中右表,按照「貨品」自定義排序,自定義序列窗口中粘貼記事本中的貨品,點擊「確定」即可將右表順序調整成和左表相同。用GIF圖演示整個操作步驟如下:貨品不相同的兩表調整順序步驟1:將「庫存數量」表格中的所有貨品複製到「實際數量」表貨品那一列下方。