動畫:一篇文章快速學會堆排序

內容介紹

堆排序簡介

前面咱們已經介紹了堆結構,也可以構造一個堆結構。若是對堆結構不瞭解的同窗能夠看一下以前的文章《動畫學堆結構,一篇一看就懂的堆結構文章》。咱們稍微回顧一下,根節點最大的堆叫作最大堆大根堆,以下圖: java

獲取最大堆的最大值,其實就是獲取堆頂的元素。對於堆這種數據結構一般是將堆頂的元素和堆中最後一個元素換位置,最大值就到了最後一個位置,而後從堆中排除這個最大的元素,當最後一個元素交換到最前面時,此時就不知足堆的性質了,咱們須要將最前面這個元素經過ShiftDown(下沉)的手段讓堆繼續知足堆的規則。算法

堆獲取最大值能夠分紅兩個步驟:編程

  1. 將堆中最前面的最大值和最後一個元素交換位置。
  2. 使用ShiftDown讓最前面的元素下沉到合適的位置,依然知足堆的性質。 動畫演示效果以下:

堆排序的思想

咱們知道堆頂的元素就是堆中的最大值,所以咱們每次從堆中取出並移除最大值(實際上是將堆頂的最大值移動到了堆的最後面),而後ShiftDown使這個二叉樹依然知足堆的規則,堆中第二大的數據就會到堆頂,再次取出堆頂的最大值,其實就是全部數據中的第二大值,依次類推,因而就完成了堆排序。api

堆排序動畫演示

通常沒有特殊要求排序算法都是升序排序,小的在前,大的在後。數組由{5, 3, 1, 9, 7, 2, 8, 6} 這8個無序元素組成。 數組

堆排序分析

如上圖所示,當咱們將堆頂的最大值9移除後,堆中的第二大值8就會到堆頂來。當咱們再次將8移除後,堆中剩餘元素的最大值會到堆頂來。微信

堆刪除8後的效果: 數據結構

堆刪除7後的效果: 性能

堆刪除6後的效果: 優化

堆刪除5後的效果: 動畫

堆刪除4後的效果:

堆刪除3後的效果:

堆刪除2後的效果:

堆刪除1後的效果:

咱們能夠看到堆排序算法的步驟:

  1. 把無序二叉樹構建成二叉堆。
  2. 循環刪除堆頂元素,移到數組尾部,調整二叉堆,獲得新堆中的最大值放到堆頂。

堆排序代碼編寫

public class HeapSort {
    public static void main(String[] args) {
        int[] arr = {6, 3, 7, 5, 8, 2, 1, 4, 9};

        heapSort(arr);
        System.out.println("堆排序後:" + Arrays.toString(arr));
    }

    /**
     * 堆排序
     * @param arr 待排序的數組
     */
    public static void heapSort(int[] arr) {
        heapify(arr);
        System.out.println("構建堆:" + Arrays.toString(arr));

        // 讓堆頂元素和堆最後一個元素交換,其實就是數組最前面數據,和後面的數據交換
        for (int i = arr.length-1; i > 0; i--) {
            swap(arr, i, 0);
            shiftDown(arr, 0, i);
        }
    }

    /**
     * heapify將無序的徹底二叉樹調整爲二叉堆
     * @param arr 待調整的數組
     */
    private static void heapify(int[] arr) {
        // 從非葉子節點開始,Shift Down將每一個子樹構建成最大堆
        for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) {
            shiftDown(arr, i, arr.length);
        }
    }

    /**
     * 下沉操做,將指定元素下沉到子樹的合適位置,使這個顆樹知足堆的規則。
     * @param arr 待調整的數組
     * @param index 要下沉的元素索引
     * @param count 堆的有效範圍
     */
    private static void shiftDown(int[] arr, int index, int count) {
        // 下沉操做時減小賦值,先保存這個要下沉的元素,後面找到合適位置直接交換
        int temp = arr[index];
        // j表示左孩子索引
        int childIndex = 2*index + 1;
        // 循環找子孩子交換位置。左孩子不能越界
        while (childIndex < count) {

            // 判斷是否有有孩子,而且右孩子是否大於左孩子
            if (childIndex+1 < count && arr[childIndex+1] > arr[childIndex]) {
                childIndex++; // 若是是,和右孩子交換
            }

            // 若是當前節點大於兩個孩子,就不須要交換
            if (temp > arr[childIndex])
                break;

            // 當前節點小於子孩子,將當前節點和較大的子孩子交換
            // 無需真正交換,記錄這個要交換的索引
            arr[index] = arr[childIndex];

            // 再判斷下一層
            index = childIndex;
            childIndex = 2*index + 1;
        }
        arr[index] = temp;
    }

    public static void swap(int[] arr, int start, int end) {
        if (start == end) return;

        int temp = arr[start];
        arr[start] = arr[end];
        arr[end] = temp;
    }
}

堆排序的複雜度

堆排序的時間複雜度:堆排序的運行時間主要是消耗在開始構建堆和在取出最大值後重建堆時的數據下沉上。在構建堆的過程當中,是從徹底二叉樹最後一個非葉子節點開始構建,最後一個非葉子節點爲(n-1)/2,將它與其孩子進行比較和互換,對於每一個非葉子節點最多會進行兩次比較,因此構建堆的時間複雜度爲0(n)。排序時,堆頂元素須要和堆中最後一個有效元素交換位置並下沉,時間複雜度是O(logi),而且有n-1次獲取堆頂最大值的過程所以,在依次獲取最大值時的時間複雜度是O(nlogn)。構建堆和獲取堆頂最大值排序是兩個先後操做,所以堆排序整體的時間複雜度是O(nlogn)。

咱們能夠看到堆排序對待排序的數據不敏感,不管數據怎麼樣,堆排序的最好,最壞,平均時間複雜度都是O(nlogn),沒有優化的空間,所以真正在排序的時候不會選擇堆排序,而是選擇優化性能更好的快速排序。

堆排序的空間複雜度:在堆排序的過程當中只須要一個額外的變量記錄要交換的數據,所以堆排序的空間複雜度爲O(1)。堆排序在獲得一個最大值後,會讓堆頂的元素下沉到合適的位置,所以堆排序是不穩定的排序算法。

總結

堆排序的過程:

  1. 將一顆徹底二叉樹構建成堆
  2. 循環獲取堆頂的最大值,放到堆的後面,並重建堆。

原創文章和動畫製做真心不易,您的點贊就是最大的支持! 想了解更多文章請關注微信公衆號:表哥動畫學編程

相關文章
相關標籤/搜索