本文正在参与掘金团队号上线活动,点击 查看大厂春招职位
一、题目描述
使用堆排序对一个非有序数组进行升序的排序。
示例:
输入:[5, 8, 6, 3, 4, 7, 1, 2]
输出:[1, 2, 3, 4, 5, 6, 7, 8]
二、思路分析
堆排序实际上就是利用,最大堆和最小堆的特性,来对数组进行排序。
最大堆: 即代表堆顶的元属是该堆里面值最大的元属
最小堆: 即代表堆顶的元属是该堆里面值最小的元属
实际上我们只需要利用最大最小堆的特性,就能轻易地对数组进行排序。
5 2 4 1 3 2 1
/ \ / \ / \ / \ / \ / \ / \
3 4 -> 3 4 -> 3 2 -> 3 2 -> 1 2 -> 1 3 -> 2 3
/ \ / \ / \ / \ / \ / \ / \
1 2 1 5 1 5 4 5 4 5 4 5 4 5
三、AC 代码
Array.prototype.heapSort = function () {
/**
* 下沉调整
* @param { Array } array 待调整的堆
* @param { Number } parentIndex 要 下沉 的父节点下标
* @param { Number } length 堆的有效大小
*/
function downAdjust(array, parentIndex, length) {
// 保存父节点用于最后的赋值
const temp = array[parentIndex]
// 获取子节点的下标 - 左子节点
let childIndex = 2 * parentIndex + 1
// 边界判断
while(childIndex < length) {
// 判断右子节点的值是否大于左子节点。
// 如果是,子节点的下标转换成右子节点
if(childIndex + 1 < length && array[childIndex + 1] > array[childIndex]) {
childIndex++
}
// 如果父节点大于子节点,则直接跳出
if(temp >= array[childIndex]) {
break;
}
// 将子节点的值移交给父节点,继续向下,下沉
array[parentIndex] = array[childIndex]
parentIndex = childIndex
childIndex = 2 * childIndex + 1
}
// 赋值
array[parentIndex] = temp
}
/**
* 堆排序 - 升序
* @param { Array } array 待调整的堆
*/
function heapSort(array) {
// 构建最大堆
for(let i = (array.length - 2) / 2; i >= 0; i--) {
downAdjust(array, i, array.length)
}
// 循环删除堆顶的元素,移到集合尾部,调整堆产生新的堆顶
for (let i = array.length - 1; i > 0; i--) {
// '最后'的元素 和 堆顶的元素进行交换
const temp = array[i]
array[i] = array[0]
array[0] = temp
// 下沉
downAdjust(array, 0, i)
}
}
heapSort(this)
}
const arr = [5, 8, 6, 3, 4, 7, 2, 1]
arr.heapSort()
console.log(arr) // [ 1, 2, 3, 4, 5, 6, 7, 8 ]
四、总结
排序在项目当中是非常常见的场景,所以排序算法是每个程序员都必须掌握并且熟练运用的算法之一。
首先需要把无序数组构建成二叉堆(数组表示法)。 需要从小到大排序(升序), 则构建成最大堆; 需要从大到小排序(降序),则构建成最小堆;
循环删除堆顶的元素,替换到二叉堆的末尾,调整堆产生新的堆顶即可。
需要注意的是,如果在数组原有基础上修改,在下沉的时候,要手动设置堆的末尾边界,来防止对转移下来的元素重新进入二叉堆,如果用额外的数组来存储,则可以不需要并且改用最小堆(使用 push),当然最大堆(使用 unshift)也是可以的。
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情