堆的預備知識
- 堆是一個完全二叉樹。
- 完全二叉樹: 二叉樹除開最後一層,其他層結點數都達到最大,最後一層的所有結點都集中在左邊(左邊結點排列滿的情況下,右邊才能缺失結點)。
- 大頂堆:根結點為最大值,每個結點的值大於或等於其孩子結點的值。
- 小頂堆:根結點為最小值,每個結點的值小於或等於其孩子結點的值。
- 堆的儲存: 堆由陣列來實現,相當於對二叉樹做層序遍歷。
堆排序演算法
現在需要對如上二叉樹做升序排序,總共分為三步:
- 將初始二叉樹轉化為大頂堆(heapify)(實質是從第一個非葉子結點開始,從下至上,從右至左,對每一個非葉子結點做shiftDown操作),此時根結點為最大值,將其與最後一個結點交換。
- 除開最後一個結點,將其餘節點組成的新堆轉化為大頂堆(實質上是對根節點做shiftDown操作),此時根結點為次最大值,將其與最後一個結點交換。
- 重複步驟2,直到堆中元素個數為1(或其對應陣列的長度為1),排序完成。
程式碼實現
1 let array = randomArray(1,100); 2 console.log(array); 3 let sortArray = heapSort(array); 4 console.log(sortArray); 5 //輸入起始值和終點值,隨機陣列 6 function randomArray(start,end){ 7 var a=[],o={},random,step=end-start; 8 while(a.length<step){ 9 random=start+parseInt(Math.random()*step); 10 if(!o["x"+random]){ 11 a.push(random); 12 o["x"+random]=1; 13 }; 14 }; 15 return a; 16 }; 17 //交換值 18 function swap(A, i, j) { 19 let temp = A[i]; 20 A[i] = A[j]; 21 A[j] = temp; 22 } 23 24 // 將 i 結點以下的堆整理為大頂堆,注意這一步實現的基礎實際上是: 25 // 假設 結點 i 以下的子堆已經是一個大頂堆,shiftDown函式實現的 26 // 功能是實際上是:找到 結點 i 在包括結點 i 的堆中的正確位置。後面 27 // 將寫一個 for 迴圈,從第一個非葉子結點開始,對每一個非葉子結點 28 // 都執行 shiftDown操作,所以就滿足了結點 i 以下的子堆已經是一大 29 //頂堆 30 function shiftDown(A, i, length) { 31 let temp = A[i]; // 當前父節點 32 // j<length 的目的是對結點 i 以下的結點全部做順序調整 33 for(let j = 2*i+1; j<length; j = 2*j+1) { 34 temp = A[i]; // 將 A[i] 取出,整個過程相當於找到 A[i] 應處於的位置 35 if(j+1 < length && A[j] < A[j+1]) { 36 j++; // 找到兩個孩子中較大的一個,再與父節點比較 37 } 38 if(temp < A[j]) { 39 swap(A, i, j) // 如果父節點小於子節點:交換;否則跳出 40 i = j; // 交換後,temp 的下標變為 j 41 } else { 42 break; 43 } 44 } 45 } 46 // 堆排序 47 function heapSort(A) { 48 // 初始化大頂堆,從第一個非葉子結點開始 49 for(let i = Math.floor(A.length/2-1); i>=0; i--) { 50 shiftDown(A, i, A.length); 51 } 52 // 排序,每一次for迴圈找出一個當前最大值,陣列長度減一 53 for(let i = Math.floor(A.length-1); i>0; i--) { 54 swap(A, 0, i); // 根節點與最後一個節點交換 55 shiftDown(A, 0, i); // 從根節點開始調整,並且最後一個結點已經為當 56 // 前最大值,不需要再參與比較,所以第三個引數 57 // 為 i,即比較到最後一個結點前一個即可 58 } 59 return A; 60 }