堆排序以及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--) { //堆化 }
堆化
- 用父結點與孩子結點比較,如果父結點比孩子結點大,則不調整位置(最大堆);否則,將孩子結點與父結點交換。
- 如果發生交換的話,再將孩子結點作為父結點,與它的孩子節點再作比較。直到葉子結點。
//compare=(a,b)=>a>b :最大堆;compare=(a,b)=>a<b :最小堆 /** * i 堆頂索引 * end 堆尾索引+1 * compare 控制是最大堆還是最小堆 */ functionadjustHeap(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