经典排序算法之堆排序
若以升序排序说明,把数组转换成最大堆积(Max-Heap Heap),这是一种满足最大堆积性质(Max-Heap Property)的二叉树:对于除了根之外的每个节点i, A[parent(i)] ≥ A[i]。
重复从最大堆积取出数值最大的结点(把根结点和最后一个结点交换,把交换后的最后一个结点移出堆),并让残余的堆积维持最大堆积性质。
最大堆积即:父节点总是大于子节点的完全二叉树
完全二叉树:若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
若将和此序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有的非叶子节点的值均不大于(或不小于)其左右孩子节点的值。
例如:序列{96,83,27,38,11,09} 对应完全二叉树如下:
在实现堆排序前我们需要解决两个问题:
(1):如何将一个无序序列建成一个堆?
(2):如何在输出堆顶元素之后,调整剩余元素成为一个新的堆?
function heapSort(arr) {
function swap(x, y) {
let tmp = arr[x];
arr[x] = arr[y];
arr[y] = tmp;
}
function maxHeap(start, end) {
let dad = start;
let son = dad * 2 + 1;
// 如果有子节点则一直调整下去
while (son <= end){
// 找出比较大的子节点
if (son+1 <= end && arr[son] < arr[son + 1]){
son ++;
}
// 和父节点比较大小
if (arr[dad] > arr[son]){
return;
} else{
// 如果父节点小于子节点则要对整个子树进行调整
// 交换父子节点,继续递归下一层
swap(dad, son);
dad = son;
son = dad * 2 + 1;
}
}
}
let len = arr.length;
// 建堆,从最后一个非叶子节点开始调整
// 子节点 c = 父节点f*2 + 1 / f*2 + 2
// 数组长度为n,最后一个节点则为n-1,则最后一个父节点为n/2 - 1
for (let i = (len >> 1) - 1; i >= 0; i--) {
maxHeap(i, len-1);
}
// 先将根节点和最后一个节点交换,然后取走根(此时为数组最后一个节点),重新建堆
for (let i = len - 1; i > 0; i--){
swap(0, i);
maxHeap(0, i - 1);
}
return arr;
}