對於結點 i ,其子結點爲 2i+1 與 2i+2 。算法
如今須要對如上二叉樹作升序排序,總共分爲三步:api
下面詳細圖解這個過程:數組
初始化大頂堆,首先選取最後一個非葉子結點(咱們只須要調整父節點和孩子節點之間的大小關係,葉子結點之間的大小關係無需調整)。設數組爲arr,則第一個非葉子結點的下標爲:i = Math.floor(arr.length/2 - 1) = 1,也就是數字4,如圖中虛線框,找到三個數字的最大值,與父節點交換。函數
而後,下標 i 依次減1(即從第一個非葉子結點開始,從右至左,從下至上遍歷全部非葉子節點)。後面的每一次調整都是如此:找到父子結點中的最大值,作交換。spa
這一步中數字六、1交換後,數字[1,5,4]組成的堆順序不對,須要執行一步調整。所以須要注意,每一次對一個非葉子結點作調整後,都要觀察是否會影響子堆順序!操作系統
此次調整後,根節點爲最大值,造成了一個大頂堆,將根節點與最後一個結點交換。3d
除開當前最後一個結點6(即最大值),將其他結點[4,5,3,1]組成新堆轉化爲大頂堆(注意觀察,此時根節點之外的其餘結點,都知足大頂堆的特徵,因此能夠從根節點4開始調整,即找到4應該處於的位置便可)。code
接下來反覆執行步驟2,直到堆中元素個數爲1:blog
堆中元素個數爲1, 排序完成。排序
// 交換兩個節點 function swap(A, i, j) { let temp = A[i]; A[i] = A[j]; A[j] = temp; } // 將 i 結點如下的堆整理爲大頂堆,注意這一步實現的基礎其實是: // 假設 結點 i 如下的子堆已是一個大頂堆,shiftDown函數實現的 // 功能是其實是:找到 結點 i 在包括結點 i 的堆中的正確位置。後面 // 將寫一個 for 循環,從第一個非葉子結點開始,對每個非葉子結點 // 都執行 shiftDown操做,因此就知足告終點 i 如下的子堆已是一大 //頂堆 function shiftDown(A, i, length) { let temp = A[i]; // 當前父節點 // j<length 的目的是對結點 i 如下的結點所有作順序調整 for(let j = 2*i+1; j<length; j = 2*j+1) { temp = A[i]; // 將 A[i] 取出,整個過程至關於找到 A[i] 應處於的位置 if(j+1 < length && A[j] < A[j+1]) { j++; // 找到兩個孩子中較大的一個,再與父節點比較 } if(temp < A[j]) { swap(A, i, j) // 若是父節點小於子節點:交換;不然跳出 i = j; // 交換後,temp 的下標變爲 j } else { break; } } } // 堆排序 function heapSort(A) { // 初始化大頂堆,從第一個非葉子結點開始 for(let i = Math.floor(A.length/2-1); i>=0; i--) { shiftDown(A, i, A.length); } // 排序,每一次for循環找出一個當前最大值,數組長度減一 for(let i = Math.floor(A.length-1); i>0; i--) { swap(A, 0, i); // 根節點與最後一個節點交換 shiftDown(A, 0, i); // 從根節點開始調整,而且最後一個結點已經爲當 // 前最大值,不須要再參與比較,因此第三個參數 // 爲 i,即比較到最後一個結點前一個便可 } } let Arr = [4, 6, 8, 5, 9, 1, 2, 5, 3, 2]; heapSort(Arr); alert(Arr);
程序註釋: 將 i 結點如下的堆整理爲大頂堆,注意這一步實現的基礎其實是:假設 結點 i 如下的子堆已是一個大頂堆,shiftDown函數實現的功能是其實是:找到 結點 i 在包括結點 i 的堆中的正確位置。後面作第一次堆化時,heapSort 中寫了一個 for 循環,從第一個非葉子結點開始,對每個非葉子結點都執行 shiftDown操做,因此就知足了每一次 shiftDown中,結點 i 如下的子堆已是一大頂堆。
複雜度分析:adjustHeap 函數中至關於堆的每一層只遍歷一個結點,由於
具備n個結點的徹底二叉樹的深度爲[log2n]+1,因此 shiftDown的複雜度爲 O(logn),而外層循環共有 f(n) 次,因此最終的複雜度爲 O(nlogn)。
堆主要是用來實現優先隊列,下面是優先隊列的應用示例:
而實現優先隊列採用普通數組、順序數組和堆的不一樣複雜度以下:
使用堆來實現優先隊列,可使入隊和出隊的複雜度都很低。