堆排序相比冒泡排序、選擇排序、插入排序而言,排序效率是最高的,本文從堆的屬性和特色出發採用圖文形式進行講解並用JavaScript將其實現,歡迎各位感興趣的開發者閱讀本文😝javascript
堆分爲兩種: 最大堆和最小堆,二者的差異在於節點的排序方式。java
在不一樣類型的堆中,每個節點都遵循堆的屬性,下方所述內容即爲堆的屬性。api
由一個徹底二叉樹組成,且樹中的全部節點都知足堆屬性,這個徹底二叉樹就是堆。數組
根據堆的屬性可知:數據結構
堆的根節點中存放的是最大或最小元素,可是其餘節點的排列順序是未知的。 例如,在一個最大堆中,最大的那一個元素老是位於index0的位置,可是最小的元素則未必是最後一個元素,惟一可以保證的是最小的元素是一個葉節點,可是不肯定是哪個。函數
用數組來實現堆,堆中的節點在數組的位置與它的父節點以及子節點的索引之間有一個映射關係。post
用公式來描述當前節點的父節點和子節點在數組中的位置(i爲當前節點的索引)測試
// 父節點的位置,向下取整(floor)
parent(i) = floor( i - 1 ) / 2)
// 左子節點的位置
left(i) = 2i +1
// 右子節點的位置,左右節點老是處於相鄰的位置
right(i) = left(i)+1 = 2i+2
複製代碼
array[parent(i)] >= array[i]
複製代碼
堆是一個徹底二叉樹,樹的高度是指從樹的根節點到最低葉節點所須要的步數。ui
樹高度正式的定義: 節點之間的邊的最大值。spa
一個高度爲h的堆有h+1層。
若是一個堆有n個節點,那麼它的高度爲 floor(log2(n))
// 例如,一個堆有15個節點,求這個堆的高度
h = floor(log2(15)) = floor(3.91) = 3
套入公式可得,堆的高度爲3
// log2(n),log爲求次方運算,log以2爲底,15的對數。若n爲8,則運算結果爲3,即2^3=8
複製代碼
// 高度爲3,即最下面一層有8個節點
2^3 = 8;
複製代碼
// 高度爲3,即其餘層的全部節點有7個
2^3 -1 = 7;
複製代碼
// 高度爲3,即堆中的節點數爲15
2^4 -1 = 16 - 1 = 15;
複製代碼
實現堆排序以前,咱們須要先將即將排序的數據構建成一個最大堆,構建完成後,根據最大堆的屬性可知,堆頂部的值最大,咱們將它取出,而後從新構建堆,直到堆中的全部數據被取出,堆排序也就完成了。
在一個徹底二叉樹中,從一個節點出發,找到它的左子樹和右子樹,將當前節點與它的兩顆子樹進行大小比較,找到兩顆樹中較大的一方,將其與當前節點進行交換,交換完畢後,當前節點所在的樹就是一個最大堆。咱們稱這個交換操做爲heapify
接下來,咱們來整理下實現思路
接下來咱們將上述思路轉化爲代碼:
/* * 1. 從一個節點出發 * 2. 從它的左子樹和右子樹中選擇一個較大值 * 3. 將較大值與這個節點進行位置交換 * 上述步驟,就是一次heapify的操做 * */
// n爲樹的節點數,i爲當前操做的節點 (找到這顆樹裏的最大節點)
const heapify = function (tree, n, i) {
if(i >= n){
// 結束遞歸
return;
}
// 找到左子樹的位置
let leftNode = 2 * i + 1;
// 找到右子樹的位置
let rightNode = 2 * i +2;
/* 1. 找到左子樹和右子樹位置後,必須確保它小於樹的總節點數 2. 已知當前節點與它的左子樹與右子樹的位置,找到最大值 */
// 設最大值的位置爲i
let max = i;
// 若是左子樹的值大於當前節點的值則最大值的位置就爲左子樹的位置
if(leftNode < n && tree[leftNode] > tree[max]){
max = leftNode;
}
// 若是右子樹的值大於當前節點的值則最大值的位置就爲右子樹的位置
if(rightNode < n && tree[rightNode] > tree[max]){
max = rightNode;
}
/* * 1. 進行大小比較後,若是最大值的位置不是剛開始設的i,則將最大值與當前節點進行位置互換 * */
if(max !== i){
// 交換位置
swap(tree,max,i);
// 遞歸調用,繼續進行heapify操做
heapify(tree,n,max)
}
};
// 交換數組位置函數
const swap = function (arr,max,i) {
[arr[max],arr[i]] = [arr[i],arr[max]];
};
複製代碼
接下來咱們測試下heapify函數
const dataArr = [23,15,34,11,23,4,19,80];
// 咱們假設當前操做節點爲數組的0號元素,咱們對0號元素進行一次heapify才作
heapify(dataArr,dataArr.length,0);
// 打印結果
console.log(dataArr);
複製代碼
執行結果以下,觀察執行結果,咱們發現,0號元素所在的樹符合最大堆的屬性
一般狀況下,咱們的數據是亂序的,沒有規律可言,此時咱們就須要將這些數據構建成堆,heapify實現堆的構建前提是:知道當前操做節點的位置,此時咱們從數據的最後一個節點的父節點出發,進行heapify操做,直至當前操做節點爲數組的0號元素時,那麼這組數據就成了一個最大堆。
接下來,咱們整理下實現思路:
接下來,咱們將上述思路轉化爲代碼:
/* * 將徹底二叉樹構建成堆 * 1. 從樹的最後一個父節點開始進行heapify操做 * 2. 樹的最後一個父節點 = 樹的最後一個子結點的父節點 * */
const buildHeap = function (tree,n) {
// 最後一個節點的位置 = 數組的長度-1
const lastNode = n -1;
// 最後一個節點的父節點
const parentNode = Math.floor((lastNode - 1) / 2);
// 從最後一個父節點開始進行heapify操做
for (let i = parentNode; i >= 0; i--){
heapify(tree, n, i);
}
};
複製代碼
接下來咱們測試下buildHeap函數
const dataArr = [23,15,34,11,23,4,19,80];
buildHeap(dataArr,dataArr.length);
console.log(dataArr);
複製代碼
觀察執行結果,咱們發現數組中的數據已經知足最大堆的屬性
咱們將最大堆構建完成後,根據最大堆的特性可知:堆的頂點爲這個堆的最大值,咱們將這個值取出,而後將堆的最後一個節點移動至堆的頂部,而後調用heapify,從新構建堆,直至最大堆中的數據所有被取出則排序完成。
接下來,咱們整理下實現思路:
接下來,咱們將上述思路轉化爲代碼:
// 堆排序函數
const heapSort = function (tree,n) {
// 構建堆
buildHeap(tree,n);
// 從最後一個節點出發
for(let i = n - 1; i >= 0; i--){
// 交換根節點和最後一個節點的位置
swap(tree,i,0);
// 從新調整堆
heapify(tree,i,0);
}
};
複製代碼
接下來咱們測試下heapSort函數
const dataArr = [23,15,34,11,23,4,19,80];
heapSort(dataArr,dataArr.length);
console.log(dataArr);
複製代碼
觀察執行結果,咱們發現數組中的數據已經按照從小到大進行排列