1. 程式人生 > 其它 >堆排序以及TopK大頂堆小頂堆求解方式(js版)

堆排序以及TopK大頂堆小頂堆求解方式(js版)

我理解的堆排序

堆排序是一種選擇排序,時間複雜度o(nlogn),空間複雜度o(1)。
資料結構底層是陣列,通過索引之間的關係可看二叉樹,父結點總是大於或者小於孩子結點。這就是堆的結構。
剛初始完的堆是佔據整個陣列的。
開始排序後,陣列分為兩個部分!前面是堆,後面是已排序完的有序子陣列。
排序時,堆頂元素和堆尾元素會交換,有序子陣列長度+1,堆長度-1;此時,原堆尾元素佔據了堆頂,可能會破壞了堆結構,所以,需要堆化,也就是堆頂元素要保持最大或者最小。
當堆只有一個元素,自動加入有序子陣列;這時有序子陣列佔據整個陣列。排序完成。

如何初始化堆

將普通陣列初始為堆。

把陣列arr看成一個二叉樹,父結點與孩子結點的關係:root:i , leftChild:2i+1, rightChild:2i+2
找到索引最大的非葉子節點。陣列長度length,假設length- 1= 2i + 1,則i=length/2 - 1。所以索引最大的非葉子結點是arr[i]。
堆化。也就是將[i,length - 1]的陣列變成堆,然後i - 1,使堆長度+1,再堆化,這樣反覆操作,直到i == 0,讓堆佔滿整個陣列。

    //初始堆
    for (let i = (arr.length >> 1) - 1; i >= 0; i--) {
       //堆化
    }

堆化

  1. 用父結點與孩子結點比較,如果父結點比孩子結點大,則不調整位置(最大堆);否則,將孩子結點與父結點交換。
  2. 如果發生交換的話,再將孩子結點作為父結點,與它的孩子節點再作比較。直到葉子結點。
//compare=(a,b)=>a>b :最大堆;compare=(a,b)=>a<b :最小堆
/**
* i 堆頂索引
* end 堆尾索引+1
* compare 控制是最大堆還是最小堆
*/
function
adjustHeap(arr, i, end, compare) { let tmp = arr[i]; let parentIndex = i; let childIndex = 2 * i + 1; while (childIndex < end) { if (childIndex + 1 < end && !compare(arr[childIndex], arr[childIndex + 1])) { childIndex++; } if (!compare(tmp, arr[childIndex])) { arr[parentIndex]
= arr[childIndex]; parentIndex = childIndex; childIndex = (childIndex << 1) + 1; } else { break; } } arr[parentIndex] = tmp; }

排序

1.一開始,堆佔據整個陣列。取出堆頂元素,與堆尾元素交換。交換後,堆尾加入有序子陣列,堆長度-1。
2.所有堆元素加入有序陣列後,排序完畢。
3.小頂堆用於降序排序,大頂堆用於升序排序。

   for (let j = arr.end- 1; j > 0; j--) {
        [arr[0], arr[j]] = [arr[j], arr[0]];//交換堆頂堆尾
        adjustHeap(arr, 0, j, compare);//堆尾索引-1,並且堆化
    }

堆排序整體程式碼

function sort(arr, compareFunction) {
    function defaultCompare(a, b) {//預設大頂堆,也就是升序排序
        return a > b;
    }
    let compare = compareFunction || defaultCompare;
    for (let i = (arr.length >> 1) - 1; i >= 0; i--) {//從後往前數,第一個非葉子結點開始初始化堆
        adjustHeap(arr, i, arr.length, compare);//堆正在變長
    }
    for (let j = arr.length - 1; j > 0; j--) {
        [arr[0], arr[j]] = [arr[j], arr[0]];//有序子陣列正在變長
        adjustHeap(arr, 0, j, compare);//堆正在縮小
    }
    //排序完成
}
//堆化
//compare=(a,b)=>a>b :大頂堆;compare=(a,b)=>a<b :小頂堆
/**
* i 堆頂索引
* end 堆尾索引+1
* compare 控制是最大堆還是最小堆
*/
function adjustHeap(arr, i, end, compare) {
    let tmp = arr[i];
    let parentIndex = i;
    let childIndex = 2 * i + 1;
    while (childIndex < end) {
        if (childIndex + 1 < end && !compare(arr[childIndex], arr[childIndex + 1])) {//找到孩子結點更大(小)的一個
            childIndex++;
        }
        if (!compare(tmp, arr[childIndex])) {//父結點不如孩子結點大(x小)的話,交換
            arr[parentIndex] = arr[childIndex];
            parentIndex = childIndex;
            childIndex = (childIndex << 1) + 1;
        } else {
            break;
        }
    }
    arr[parentIndex] = tmp;
}

使用大頂堆(小頂堆)解決TopK問題

拿小頂堆舉例來說,堆頂就是當前K箇中最小的。如果最大的前K個元素都在這個堆中,那堆頂就是第K大的那個元素。

初始化堆,堆的大小為k。(直接拿陣列前K個元素初始就行)
從arr[k](第k+1個元素)開始和堆頂作比較,如果大於堆頂就和堆頂交換;交換後,堆頂可能不是堆中最小元素,所以需要堆化。
不斷地有更大的元素被換入堆中,並且堆頂又能保持堆中最小。
當arr[length-1]也比較完成後,堆中就包含了最大的K個元素了,並且堆頂arr[0]就是第K大的元素。時間複雜度o(nlogk)

function findTopK(arr, k, compareFunction) {
    function defaultCompare(a, b) {
        return a > b;
    }
    let compare = compareFunction || defaultCompare;
    for (let i = (k >> 1) - 1; i >= 0; i--) {//將[0,k-1]區間作為堆
        adjustHeap(arr, i, k, compare);
    }
    for (let j = k + 1; j < arr.length; j++) {
        if (!compare(arr[j], arr[0])) {//與堆頂作比較,比堆頂大(小)則替換原堆頂,並堆化
            [arr[j], arr[0]] = [arr[0], arr[j]];
            adjustHeap(arr, 0, k, compare);
        }
    }
    return arr[0];//返回堆頂(topK)
}

————————————————
版權宣告:本文為CSDN博主「Small Tornado」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/XIAOLONGJUANFENG/article/details/114195541