堆排序相比冒泡排序、选择排序、插入排序而言,排序效率是最高的,本文从堆的属性和特色出发采用图文形式进行讲解并用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);
复制代码
观察执行结果,咱们发现数组中的数据已经按照从小到大进行排列