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


排序算法相必大家都見過很多種,例如快速排序、歸並排序、冒泡排序等等。今天,我們就來簡單講講堆排序

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

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

 

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

 

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

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

今天,我們暫且選擇以最小堆來作為例子。

基於堆頂這個特點,我們就可以來實現我們的堆排序了。

大家看下面一個例子:

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

我們把堆頂這個節點刪除,然后把刪除的節點放在一個輔助數組help里。

顯然,這個被刪除的節點,是堆中最小的節點。接下來,我們繼續刪除二叉堆的堆頂,然后把刪除的元素還是存放在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個數就可以了。至於代碼如何實現,這個我就不給代碼了,大家可以動手敲一敲。

推薦閱讀:

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

獲取更多原創文章,可以關注下我的公眾號:苦逼的碼農,我會不定期分享一些資源和軟件等。后台回復禮包送你一份時下熱門的資源大禮包。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM