詳解堆排序

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

轉自:靜默虛空

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恤¥搶先預覽(長按複製整段文案,打開手機淘寶即可進入活動內容)

相關焦點

  • 堆排序算法C語言詳解
    在學習堆排序之前,首先需要了解堆的含義:在含有 n 個元素的序列中,如果序列中的元素滿足下面其中一種關係時,此序列可以稱之為堆。
  • 快速排序和堆排序
    龜哥今天和大家分享下快速排序,堆排序算法。首先快速排序的思想,給定一個無序的數組,首先用兩個指針left和right分別指向數組的頭部和尾部,將數組的第一個元素array[0]作為和left,right指針比較的元素。
  • 堆排序就這麼簡單
    一、堆排序介紹來源百度百科:堆排序(Heapsort)是指利用堆積樹(堆)這種數據結構所設計的一種排序算法,它是選擇排序的一種。可以利用數組的特點快速定位指定索引的元素。堆分為大根堆和小根堆,是完全二叉樹。前面我已經有二叉樹入門的文章了,當時講解的是二叉查找樹,那上面所說的完全二叉樹是怎麼樣的一種二叉樹呢??
  • 堆排序與拓撲排序(Java模板)
    1.堆排序思想(1) 以升序排序為例,我們需要構建一個小根堆。第一種方法:用數組模擬小根堆。忘記了的朋友可以看看之前的文章《堆模板(Java)》認真複習一下~第二種方法:使用 java.util.PriorityQueue ,相當於一個堆結構。
  • 第二篇,選擇排序算法:簡單選擇排序、堆排序
    此為第二篇,選擇排序算法:簡單選擇排序、堆排序。
  • 堆排序算法
    堆是一種數據結構,一種叫做完全二叉樹的數據結構。堆是具有以下性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱為大頂堆;或者每個結點的值都小於或等於其左右孩子結點的值,稱為小頂堆。大頂堆:a[i] >=a[2i+1] && ar[i] >= a[2i+2] 小頂堆:a[i] <= a[2i+1] && a[i] <= a[2i+2] 堆排序的基本思想:
  • 圖解:什麼是堆排序?
    堆排序 要學習今天的堆排序(Heap Sort),我們以一個數組  arr = [5、1、4、2、8、4] 開始(這個數組我們之前講排序算法常用的):我們首先以這個數組建立一個大頂堆,插入結點 5 作為根結點
  • 動畫 | 什麼是堆排序?
    而堆排序因為二叉堆的性質,堆頂就是最大的元素,查找次數只有一次,但是將無序轉成有序中間還需要一個預處理過程:構造堆有序。 堆有序並不代表數組有序,堆有序是滿足 二叉堆 性質的: 1.父節點的鍵值總是優先於任何一個子節點的鍵值; 2.左右子樹都是一個二叉堆。
  • 堆排序原理及實現
    小白們也很熟悉的:冒泡,歸併,簡單選擇,歸併,堆排序。那它們區別是什麼呢?需要好好梳理一下了。今天咱們就先瞅瞅感覺比較抽象的堆算法吧。什麼是堆?堆是具有下列性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱為大頂堆;或者每個結點的值都小於或等於其左右子結點的值,稱為小頂堆。
  • 優先級隊列與堆排序
    如下圖,以對S O R T E X A M P L E 排序為例,首先本地構造一個最大堆,即對節點進行Sink操作,使其符合二叉堆的性質。然後再重複刪除根節點,也就是最大的元素,操作方法與之前的二叉堆的刪除元素類似。
  • 到底什麼是堆排序 PHP
    每個節點的值都大於等於子樹中每個節點值的堆叫最大堆,反之為最小堆。堆排序的時間複雜度為O(nlogn)。堆是一種特殊的二叉樹,它滿足以下兩點:1. 堆是一個完全二叉樹2. 堆中每個節點都必須大於等於(或小於等於) 其子樹的每個節點
  • 一文讀懂堆排序
    (點擊上方公眾號,可快速關注)轉自:劉毅https://61mon.com/index.php/archives/202/好文投稿, 請點擊 → 這裡了解詳情堆排序是利用堆的性質進行的一種選擇排序下面先討論一下堆。堆實際上是一棵完全二叉樹,其滿足性質:任何一結點大於等於或者小於等於其左右子樹結點。堆分為大頂堆和小頂堆,滿足 「任何一結點大於等於其左右子樹結點」 的稱為大頂堆,滿足 「任何一結點小於等於其左右子樹結點」 的稱為小頂堆。由上述性質可知:大頂堆的堆頂肯定是最大的,小頂堆的堆頂是最小的。
  • 八十四、堆排序解決TopK問題
    「@Author:Runsen」上次介紹了堆排序,這次介紹堆排序常見的應用場景TopK問題。利用堆求TopK問題TopK問題是一個堆排序典型的應用場景。至於為什麼TopK問題最佳的答案是堆排序?其實在空間和時間的複雜度來考量,雖說快排是最好的排序算法,但是對於100億個元素從大到小排序,然後輸出前 K 個元素值。可是,無論我們掌握的是快速排序算法還是堆排序算法,在排序的時候,都需要將全部的元素讀入到內存中。
  • ...選擇,插入,希爾,歸併,快排,堆排序,計數排序,桶排序,基數排序)
    最佳情況:O(nlogn)最壞情況:O(n*2)平均情況:O(nlogn)八、堆排序(heap sort)一種利用堆概念來排序的選擇排序,是一種不穩定的排序。思路:利用堆這種數據結構設計的排序算法。堆積是一個近似完全的二叉樹的結構,並且擁有堆積的性質:子節點的鍵值或者索引總小於(大於)它的父節點。描述:(1)將初始待排序序列(r1,r2,r3,...
  • 八大經典排序算法詳解
    array[j] = array[j-gap]; } array[j] = tmp; } } }堆排序•預備知識:二叉堆是完全二元樹(二叉樹)或者是近似完全二元樹(二叉樹)。
  • 經典排序算法和Python詳解之(一)選擇排序和二元選擇排序
    本文源自微信公眾號【Python編程和深度學習】原文連結:經典排序算法和Python詳解之(一)選擇排序和二元選擇排序,歡迎掃碼關注鴨!掃它!掃它!掃它排序算法是《數據結構與算法》中最基本的算法之一。排序就是對於一個列表,按照某關鍵字遞增或遞減的順序進行操作。
  • 【offerMe--數據結構】----排序算法
    博客連結(詳解)https://blog.csdn.net/weixin_41563161/article/details/101637859 敘述:先將數據進行分組,通過插入排序的方式讓數據基本有序,然乎再進行插入排序,能夠讓插入排序最壞情況的概率降低;分組的方法:固定步長取值分為一組的方法
  • 排序算法問題:穩定排序與不穩定排序
    首先,排序算法的穩定性大家應該都知道,通俗地講就是能保證排序前2個相等的數其在序列的前後位置順序和排序後它們兩個的前後位置順序相同。在簡單形式化一下,如果Ai = Aj,Ai原來在位置前,排序後Ai還是要在Aj位置前。其次,說一下穩定性的好處。排序算法如果是穩定的,那麼從一個鍵上排序,然後再從另一個鍵上排序,第一個鍵排序的結果可以為第二個鍵排序所用。
  • JavaScript數組 - 引用詳解
    基本數據類型詳解在學習數組引用詳解前,我們先來看基本數據類型的詳解舉個小例子:我們聲明一個a = 10;然後聲明一個函數,這個函數裡面有個參數為a把這個參數的a改成5,a = 5; 並且再加上alert(a);函數外我們先去alert(a);再調用這個函數把a寫在裡面傳進去
  • 你所知道的十大排序算法的總結(冒泡,選擇,插入,希爾,歸併,快排,堆...
    最佳情況:O(nlogn)最壞情況:O(n*2)平均情況:O(nlogn)八、堆排序(heap sort)一種利用堆概念來排序的選擇排序,是一種不穩定的排序。思路:利用堆這種數據結構設計的排序算法。堆積是一個近似完全的二叉樹的結構,並且擁有堆積的性質:子節點的鍵值或者索引總小於(大於)它的父節點。