【小小前端】前端排序演算法第三期(不簡單選擇排序-堆排序)

.Ping發表於2020-03-18

堆排序(Heap Sort)

堆排序(Heapsort)是指利用堆這種資料結構所設計的一種排序演算法。堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。

什麼是堆?

堆其實是一種特殊的樹。只要滿足這兩點,它就是一個堆。

  • 堆是一個完全二叉樹(除了最後一層,其他層的節點個數都是滿的,最後一層的節點都靠左排列)。
  • 堆中每一個節點的值都必須大於等於(或小於等於)其子樹中每個節點的值。

對於每個節點的值都大於等於子樹中每個節點值的堆,我們叫作大頂堆。(正序排序)

對於每個節點的值都小於等於子樹中每個節點值的堆,我們叫作小頂堆。(逆序排序)

【小小前端】前端排序演算法第三期(不簡單選擇排序-堆排序)

其中圖 1 和 圖 2 是大頂堆,圖 3 是小頂堆,圖 4 不是堆。除此之外,從圖中還可以看出來,對於同一組資料,我們可以構建多種不同形態的堆。

演算法描述

  1. 將待排序序列構建成一個大頂堆,此堆為初始的無序區(其實此時arr[0]已經是最大值,如上圖1圖2,需要繼續對子節點進行排序)
  2. 將堆頂與最後一個元素交換,得到有序序列和無序序列,無序序列的所有元素小於有序序列最小的元素
  3. 交換後堆被破壞,重新構建大頂堆,重複2步驟arr.length - 1 次,此時排序完成;

動圖演示

表示一個圖壓根看不懂(其實兩個也看不懂)

【小小前端】前端排序演算法第三期(不簡單選擇排序-堆排序)

【小小前端】前端排序演算法第三期(不簡單選擇排序-堆排序)

程式碼實現

            let arr = [3, 45, 16, 8, 65, 15, 36, 22, 19, 1, 96, 12, 56, 12, 45];
            let len = arr.length;
            let heapSort = arr => {
                // 構建大頂堆
                for (let i = Math.trunc(len / 2 - 1); i >= 0; i--) {
                    heapify(arr, i, len);
                }
                // 構建完成之後,對剩餘元素繼續構建大頂堆
                for (let i = Math.floor(arr.length - 1); i > 0; i--) {
                    // 將根元素(最小或最大元素)與最後一個元素交換
                    swap(arr, 0, i);
                    // 繼續進行構建大頂堆
                    heapify(arr, 0, i);
                }
                console.log(arr);
            };
            let swap = (arr, i, j) => {
                let temp;
                temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            };
            // 將 i 結點以下的堆整理為大頂堆,注意這一步實現的基礎實際上是:
            // 假設結點 i 以下的子堆已經是一個大頂堆,heapify 函式實現的
            // 功能是實際上是:找到 結點 i 在包括結點 i 的堆中的正確位置。
            // 後面將寫一個 for 迴圈,從第一個非葉子結點開始,對每一個非葉子結點
            // 都執行 heapify 操作,所以就滿足了結點 i 以下的子堆已經是一大頂堆
            let heapify = (arr, i, length) => {
                let temp = arr[i];
                for (let j = 2 * i + 1; j < length; j = 2 * j + 1) {
                    if (j + 1 < length && arr[j] > arr[j + 1]) {
                        j++;
                    }
                    if (temp > arr[j]) {
                        swap(arr, i, j);
                        i = j;
                    } else {
                        break;
                    }
                }
            };
            heapSort(arr);
複製程式碼

結果:

【小小前端】前端排序演算法第三期(不簡單選擇排序-堆排序)

堆排序包括建堆和排序兩個操作,建堆過程的時間複雜度是 O(n),排序過程的時間複雜度是 O(nlogn),所以,堆排序整體的時間複雜度是 O(nlogn)。 最佳情況:T(n) = O(nlogn)。 最差情況:T(n) = O(nlogn)。 平均情況:T(n) = O(nlogn)。

另外從堆的行政來看,堆排序是不穩定的排序。

總結

堆排序也是一種選擇排序,從名字上乍一看很高深的感覺,但是結合堆的特性來看其實比其他排序更簡單(至少比希爾排序簡單hhh),重點在於理解什麼是堆,以及大頂堆和小頂堆

參考

下期預告

【小小前端】前端排序演算法第四期(另一種交換排序之快速排序)

相關文章