之前的一、二文章讲的堆排序,是通过另一个数组存储最大的值,然后拼接的。
接下来讲的,将是不会创建多余的空间,进而对这个数组进行最大堆排序。
思考:
- 假如一个满足最大堆要求的数组,
[62, 41, 28, 30, 22, 16, 19, 15, 13, 17]
。 - 如何能不创建新的空间(数组),进行排序?
- 我们仍然是利用shift down操作。
- 我们将17和62调换位置,然后shift down的时候将最后一位62排除出去。
- 这样不包括62的数组就是最大堆,然后重复执行上面的操作。
- 注意:我们的数组以索引0开始了,而不是之前的索引1。
- 这样我们每个父节点下面的子节点索引的计算就需要改变。
- left:2i+1 / right:2i+2
- 最后一个非叶子节点:parseInt((arr.length -1) / 2)
// 原地堆排序
let arr = [62, 41, 30, 28, 16, 22, 13, 19, 17, 15]
function swap(arr, leftIndex, rightIndex) {
[arr[leftIndex], arr[rightIndex]] = [arr[rightIndex], arr[leftIndex]]
}
function shiftDown(arr, n, k) {
while (2 * k + 1 < n) {
let j = 2 * k + 1
if (j + 1 < n && arr[j + 1] > arr[j]) {
j += 1
}
if (arr[k] >= arr[j]) {
break;
}
swap(arr, k, j)
k = j
}
return arr;
}
function heapSort(arr) {
for (let i = arr.length - 1; i >= 0; i--) {
// 先交换
// 每次的第一项与最后一项交换
// 每次最后一项通过i--排除掉
swap(arr, 0, i)
// 生成最大堆
// 每次从0开始,到i结束生成最大堆
shiftDown(arr, i, 0)
}
return arr
}
console.log(heapSort(arr))
这里再来复习一下shift down的思路:
- 取出最后一个元素。我们现在不需要取出了,而是直接换位置。
- 第一个节点现在是最小值,跟下面的两个节点比较。
- 下面两个节点那个大,跟大的交换位置。
- 不断重复交换位置,判断大小的操作。
- 原地堆排序就结束了。
排序算法的稳定性
定义:
稳定排序:对于相等的元素,在排序后,原来靠前的元素依然靠前。相等元素的相对位置没有发生改变。
例子:
我们用图来理解一下:
什么叫做相等元素的相对位置没有发生改变?
上图中的第一行的3个3与第二行的3个3,前后位置没有发生变化。
稳定的排序在生活中有很多应用,如成绩单按照学生的分数排序。排序完成,按照学生名字的字典排序。这里先不介绍字典。只是简单的介绍一下稳定排序的应用!