【算法與數據結構專場】堆排序是什麼鬼?

排序算法相必你們都見過不少種,例如快速排序、歸併排序、冒泡排序等等。今天,咱們就來簡單講講堆排序vue

在上一篇中,咱們講解了二叉堆,今天的堆排序算法主要就是依賴於二叉堆來完成的,不清楚二叉堆是什麼鬼的,能夠看下:java

【算法與數據結構】二叉堆是什麼鬼?算法

 

用輔助數組來實現堆排序算法

 

假如給你一個二叉堆,根據二叉堆的特性,你會怎麼使用二叉堆來實現堆排序呢?數組

咱們都知道,二叉堆有一個很特殊的節點 —- 堆頂,堆頂要嘛是全部節點的最大元素,要嘛是最小元素,這主要取決於這個二叉堆是最小堆仍是最大堆markdown

今天,咱們暫且選擇以最小堆來做爲例子。數據結構

基於堆頂這個特色,咱們就能夠來實現咱們的堆排序了。app

你們看下面一個例子:測試

對於一個如圖有10個節點元素的二叉堆:ui

咱們把堆頂這個節點刪除,而後把刪除的節點放在一個輔助數組help裏。url

顯然,這個被刪除的節點,是堆中最小的節點。接下來,咱們繼續刪除二叉堆的堆頂,而後把刪除的元素仍是存放在help數組裏。

顯然,第二次刪除的節點,是原始二叉堆中的第二小節點。

繼續刪除

繼續連續6次刪除堆頂,把刪除的節點一次放入help數組。

二叉堆中只剩最後一個節點了,這個節點同時也是原始二叉堆中的最大節點,把這個節點繼續刪除了,仍是放入help數組裏。

此時,二叉堆的元素被刪除光了,觀察一下help數組。這是一個有序的數組,實際上,經過從二叉堆的堆頂逐個取出最小值,存放在另外一個輔助的數組裏,當二叉堆被取光之時,咱們就完成了一次堆排序了。

其實無需輔助數組

 

在上面的堆排序過程當中,咱們使用了一個輔助數組help。可事實上,咱們真的須要輔助數組嗎?

上篇講二叉堆的時候,咱們說過。二叉堆在實現的時候,是採起數組的形式來存儲的。

從二叉堆中刪除一個元素,爲了充分利用空間,其實咱們是能夠把刪除的元素直接存放在二叉堆的最後一個元素那裏的。例如:

刪除堆頂,把刪除的元素放在最後一個元素。

繼續刪除,把刪除的元素放在最後第二個位置

繼續刪除,把刪除的元素放在最後第三個位置

以此類推….

這樣,對於一個含有n個元素的二叉堆,通過n-1(不用刪除n次)次刪除以後,這個數組就是一個有序數組了。

因此,給你一個無序的數組,咱們須要把這個數組構建成二叉堆,而後在經過堆頂逐個刪除的方式來實現堆排序。

其實,也不算是刪除了,至關因而把堆頂的元素與堆尾部在交換位置,而後在經過下沉的方式,把二叉樹恢復成二叉堆。

代碼以下:

public class HeapSort {
    /** * 下沉操做,執行刪除操做至關於把最後 * * 一個元素賦給根元素以後,而後對根元素執行下沉操做 * @param arr * @param parent 要下沉元素的下標 * @param length 數組長度 */
    public static int[] downAdjust(int[] arr, int parent, int length) {
        //臨時保證要下沉的元素
        int temp = arr[parent];
        //定位左孩子節點位置
        int child = 2 * parent + 1;
        //開始下沉
        while (child < length) {
            //若是右孩子節點比左孩子小,則定位到右孩子
            if (child + 1 < length && arr[child] > arr[child + 1]) {
                child++;
            }
            //若是父節點比孩子節點小或等於,則下沉結束
            if (temp <= arr[child])
                break;
            //單向賦值
            arr[parent] = arr[child];
            parent = child;
            child = 2 * parent + 1;
        }
        arr[parent] = temp;
        return arr;
    }

    //堆排序
    public static int[] heapSort(int[] arr, int length) {
        //構建二叉堆
        for (int i = (length - 2) / 2; i >= 0; i--) {
            arr = downAdjust(arr, i, length);
        }
        //進行堆排序
        for (int i = length - 1; i >= 1; i--) {
            //把堆頂的元素與最後一個元素交換
            int temp = arr[i];
            arr[i] = arr[0];
            arr[0] = temp;
            //下沉調整
            arr = downAdjust(arr, 0, i);
        }
        return arr;
    }
    //測試
    public static void main(String[] args) {
        int[] arr = new int[]{1, 3, 5,2, 0,10,6};
        System.out.println(Arrays.toString(arr));
        arr = heapSort(arr, arr.length);
        System.out.println(Arrays.toString(arr));
    }
}

對於堆的時間複雜度,我就直接給出了,有興趣的能夠本身推理下,仍是不難的。堆的時間複雜度是 O (nlogn)。空間複雜度是 O(1)。

這裏可能你們會問,堆排序的時間複雜度是O (nlogn),像快速排序,歸併排序的時間複雜度也是 O(nlogn),那我在使用的時候該如何選擇呢?

這裏說明一下:快速排序是平均複雜度 O(logn),實際上,快速排序的最壞時間複雜度是O(n^2。),而像歸併排序,堆排序,都穩定在O(nlogn)

我給出一個問題,例如給你一個擁有n個元素的無序數組,要你找出第 k 個大的數,那麼你會選擇哪一種排序呢?

顯然在這個問題中,選用堆排序是最好的,咱們不用把數組所有排序,只須要排序到前k個數就能夠了。至於代碼如何實現,這個我就不給代碼了,你們能夠動手敲一敲。

推薦閱讀:

【算法與數據結構】二叉堆是什麼鬼?

獲取更多原創文章,能夠關注下個人公衆號:苦逼的碼農,我會不按期分享一些資源和軟件等。後臺回覆禮包送你一份時下熱門的資源大禮包。

相關文章
相關標籤/搜索