極客算法訓練筆記(八),十大經典排序之堆排序,被樹耽誤的數組

十大經典排序算法江山圖

圖片十大經典排序算法江山圖java

堆排序在排序複雜性的研究中有着重要的地位,由於他是咱們所知的惟一可以同時最優的利用空間和時間的方法,當空間十分緊張的時候(例如嵌入式系統或者低成本的移動設備中)他很流行,由於他只用幾行就能實現較好的性能。可是現代操做系統中不多使用他,由於他沒法利用緩存,這一點很致命。數組元素不多和相鄰的其餘元素進行比較,所以緩存未命中的次數要遠遠高於大多數比較都在相鄰元素間的算法,如快速排序,歸併排序,甚至是希爾排序。面試

什麼是堆?

定義

堆(英語:Heap)是計算機科學中的一種特別的徹底二叉樹,知足特性"給定堆中任意節點P和C,若P是C的母節點,那麼P的值會小於等於(或大於等於)C的值"。 摘自維基百科。算法

首先堆是一個徹底二叉樹(除了最後一層,其餘層的節點個數都是滿的,最後一層的節點都靠左排列),這點很重要,直接決定了數組下標和左右父節點的對應關係(關係往下看)。數組

分類

最小堆(min heap):母節點的值恆小於等於子節點的值;緩存

最大堆(max heap):母節點的值恆大於等於子節點的值;ide

圖片性能

值得注意,這個地方對哦左右子節點的大小沒有要求。url

並且,堆通常用數組來存儲,而不是樹。由於樹須要爲其左右子節點分配指針空間,而數組使用數組下標來表示左右子節點的位置,能夠說堆是被樹耽誤了的數組,頂着樹的光環成功勸退了一大批想學的人,結果實際上幹着數組該乾的事兒,算法史上最大的騙局,掛羊肉賣狗肉spa

父節點和左右子節點在數組中的位置關係:操作系統

parent(i) = arr((i - 1)/2)
left(i)   = 2i + 1
right(i)  = 2i + 2

做用

  1. 構建優先隊列
  2. 堆排序
  3. 找最值
  4. 你猜,面試官考你這個啥心態

值得注意,搜索不是堆的目的,並非每個最小(大)堆都是一個有序數組!要將堆轉換成有序數組,須要使用堆排序,此處歡迎咱們的堆排序隆重登場。

堆排序

正由於堆只是父子之間大小關係肯定,左右子節點直接大小不肯定,因此纔要堆排序將全部的元素有序排序。

算法描述

以構建大頂堆爲例:

  1. 將待排序序列構成一個大頂堆,此時,整個序列的最大值就是堆頂的根節點;
  2. 將其與末尾元素進行交換,此時末尾就是最大值;
  3. 而後將剩餘n-1個元素從新構形成一個大頂堆,就會獲得n個元素的次大值;
  4. 反覆執行前面2,3步,便能獲得一個有序序列

動圖演示

堆排序

圖解堆排序

根據數組構造大頂堆:

  1. 初始數組爲:[6,1,2,3,0,4,5,7]
  2. 按照徹底二叉樹,將數字依次填入,獲得圖中第一幅圖;
  3. 從最下面的非葉子結點開始(非葉子結點爲 arr.length/2-1 = 8/2-1 =3,父節點是arr[0],arr[3]也就是3)調整,根據性質,左右子節點中小的數字往上移動;
  4. 從下至上,從右往左,遞歸,直到根節點

構造大頂堆圖片

一次調整以後7成功上位,獲得數組最大的值7,與此同時數組對應的大頂堆構造完成。

由下至上的堆有序化

圖片交換調整

這裏能夠看到,交換調整以後最大的元素到了最下面,最終會是越下面的數越大,所以構造大頂堆獲得的是降序,升序要構造小頂堆。

代碼實現

import com.sun.tools.javac.util.ArrayUtils;

/**
 * @author by zengzhiqin
 * 2020-12-10
 */
public class HeapSort {

    public static int[] heapSort (int[] arr) {
        if (arr ==  null && arr.length == 0) {
            throw new RuntimeException("數組不合法");
        }

        // 構建堆(從最下面葉子結點層的上一層,即倒數第二層的第一個開始進行構建調整)
        for (int i = arr.length / 2 -1; i >= 0; i--) {
            adjustDown(arr, i, arr.length);
        }

        // 循環調整下沉
        for (int i = arr.length -1; i >= 0; i--) {
            swap(arr, 0, i);
            adjustDown(arr, 0, i);
        }

        return arr;
    }

    /**
     * 調整
     * @param arr
     * @param parent
     * @param length
     */
    public static void adjustDown (int[] arr, int parent, int length) {
        // 臨時元素保存要下沉的元素
        int temp = arr[parent];
        // 左節點的位置
        int leftChild = 2 * parent + 1;
        // 開始往下調整
        while (leftChild < length) {
            // 若是右孩子節點比左孩子大,則定位到右孩子 (父節點只是比左右孩子都大,左右孩子大小不肯定)
            if(leftChild + 1 < length && arr[leftChild] < arr[leftChild + 1]) {
                // 此時leftChild其實是右孩子,但始終是左右裏面最大的
                leftChild ++ ;
            }
            //  大頂堆條件被破壞了
            if (arr[leftChild] <= temp)  {
                break;
            }
            // 把子節點中大的值賦值給父節點
            arr[parent] = arr[leftChild];
            // 大的子節點爲父節點,調整它的左右子節點
            parent = leftChild;
            leftChild = 2 * parent + 1;
        }
        arr[parent] = temp;
    }

    /**
     * 交換數組中兩個元素
     * @param arr 數組
     * @param i 父元素
     * @param index 元素2
     */
    public static void swap (int[] arr, int i, int index) {
        int temp = arr[i];
        arr[i] = arr[index];
        arr[index] = temp;
    }

    public static void main(String[] args) {
        //int[] arr = {5, 7, 8, 3, 1, 2, 4, 6, 8};
        int[] arr = {3, 1, 2, 4};
        //int[] arr = {1, 2, 3};
        arr = heapSort(arr);
        for (int i=0; i<arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

穩定性分析

不穩定。

時間複雜度分析

nlog(n)。

大頂堆構造使用 O(N) 的時間,元素調整下濾須要 O(logN),須要下濾 N-1 次,因此總共須要 O(N+(N-1)logN) = O(NlogN)。從過程能夠看出,堆排序,無論最好,最壞時間複雜度都穩定在O(NlogN)。

空間複雜度分析

原地排序,空間複雜度O(1)

相關文章
相關標籤/搜索