堆排序(三,原地堆排序)

481 阅读2分钟

之前的一、二文章讲的堆排序,是通过另一个数组存储最大的值,然后拼接的。

接下来讲的,将是不会创建多余的空间,进而对这个数组进行最大堆排序。

思考:

  • 假如一个满足最大堆要求的数组,[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的思路:

  • 取出最后一个元素。我们现在不需要取出了,而是直接换位置。
  • 第一个节点现在是最小值,跟下面的两个节点比较。
  • 下面两个节点那个大,跟大的交换位置。
  • 不断重复交换位置,判断大小的操作。
  • 原地堆排序就结束了。

排序算法的稳定性

定义:

稳定排序:对于相等的元素,在排序后,原来靠前的元素依然靠前。相等元素的相对位置没有发生改变。

例子:

我们用图来理解一下:

image.png 什么叫做相等元素的相对位置没有发生改变? 上图中的第一行的3个3与第二行的3个3,前后位置没有发生变化。 稳定的排序在生活中有很多应用,如成绩单按照学生的分数排序。排序完成,按照学生名字的字典排序。这里先不介绍字典。只是简单的介绍一下稳定排序的应用!