排序算法之堆排序

這裏是傳送門⇒總結:關於排序算法html



平均時間複雜度 最優時間複雜度 最差時間複雜度 空間複雜度 穩定性
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不穩定


堆排序是一種利用「堆」數據結構而設計的排序算法。在升序序列中,利用的是「最大堆」,「最大堆」是「一棵任一父結點均大於或等於其子結點的徹底二叉樹」。算法

  • 算法描述
    • 將待排序列初始化爲一個「最大堆」,那麼由「最大堆」的性質,如今的堆頂元素是待排序列中的最大值
    • 把堆頂元素跟最後一個元素交換,堆長度-1
    • 將堆中剩下的元素從新調整爲「最大堆」
    • 重複以上操做...
    • 那麼堆排序的過程其實就是不斷調整堆的過程,每次調整成堆後都會獲得未排序區間中的最大值
  • 調整堆的具體實現
    • 須要瞭解結點上浮(shiftUp)、結點下沉(shiftDown)
    • 這裏須要用到的是結點下沉
    • 因爲「最大堆」的性質是「任一父結點均大於或等於其子結點」
    • 須要對全部的父結點進行檢查,看是否知足性質,不知足則調整
    • 檢查順序是從最後一個父結點開始,到根結點結束
    • 順序如此的緣由是:若從根結點開始檢查,那麼根結點的值只能是根結點與其直接後繼中的最大值,而不是整個堆的最大值;而如果從最後一個父結點開始檢查,就能夠把該子樹的最大值放在該子樹的根結點(即最後一個父結點)上,供上層父結點比較,就能把更大的值放在更上層
    • 結點上浮的實現與下沉相似,這裏僅給出代碼,實際上在升序的堆排序算法中不需用到
  • JS實現
// 結點上浮:在插入二叉樹的最後一個位置時用到,此處沒有用到
// 此處的i是徹底二叉樹裏的順序,與數組裏的順序相比大了1
function MaxHeapShiftUp(array, i) {
    while (i / 2 >= 1) {
        var index = i - 1;
        var pIndex = Math.floor(i / 2) - 1;
        if (array[index] > array[pIndex]) {
            Swap(array, index, pIndex);
        }
        i = pIndex + 1;
    }
}

// 結點下沉
// 此處的i是徹底二叉樹裏的順序,與數組裏的順序相比大了1
// 此處傳入的array會被直接改變
function MaxHeapShiftDown(array, i, len) {
    while (i * 2 <= len) {
        var index = i - 1;
        var sIndex = i * 2 - 1;
        var max = sIndex;
        if (sIndex + 1 < len && array[sIndex] < array[sIndex + 1]) {
            max += 1;
        }
        if (array[index] < array[max]) {
            Swap(array, index, max);
        }
        i = max + 1;
    }
}

// 初始化堆
// 此處傳入的array會被直接改變
function BuildMaxHeap(array) {
    var len = array.length;
    for (var i = Math.floor(len / 2); i >= 1; i--) {
        MaxHeapShiftDown(array, i, len);
    }
}

// 此處傳入的array會被直接改變
function HeapSort(array) {
    var len = array.length;
    BuildMaxHeap(array);
    for (var i = len - 1; i > 0; i--) {
        Swap(array, 0, i);
        MaxHeapShiftDown(array, 1, --len);
    }
}

爲何建堆的時候(即BuildMaxHeap函數中),最後一個非葉子結點是Math.floor(len / 2)?緣由看這個筆記:關於徹底二叉樹數組

  • 結點下沉的遞歸作法
// 這個是遞歸的作法
// 此處的i是徹底二叉樹裏的順序,與數組裏的順序相比大了1
function MaxHeapShiftDown_v2(array, i, len) {
    var left, right, max;
    max = left = 2 * i;
    if (left > len) {
        return;
    }
    right = left + 1;
    if (right < len && array[right] > array[left]) {
        max = right;
    }
    if (array[i] < array[max]) {
        Swap(array, i, max);
        MaxHeapShiftDown_v2(array,max,len);
    }
}
  • 分析
    • 上文表中堆排序的複雜度數據是非遞歸作法的複雜度
    • 執行一次MaxHeapShiftDown函數的時間複雜度爲O(logn)
    • 考慮須要時間最長的狀況,即對根結點進行下沉,那麼執行比較的次數不會超過徹底二叉樹的深度:[log2n]向下取整 + 1,(深度怎麼算的看這個筆記:關於徹底二叉樹)其中n爲結點個數,即時間複雜度爲O(logn)
    • 由於建堆時調用了Math.floor(n / 2)MaxHeapShiftDown函數,因此建堆的時間複雜度爲O(nlogn)
    • 而堆排序的時間 = 建堆的時間 + 調整堆的時間 = 執行Math.floor(n / 2) + n - 1MaxHeapShiftDown函數的時間,即堆排序的時間複雜度爲 O(nlogn)
    • 該排序屬於原地排序,空間複雜度S(n) = O(1)
相關文章
相關標籤/搜索