[前端]_js中的堆排序

204 阅读4分钟

一开始我很迷惑,js中有堆这种东西吗,数组和堆怎么做转换?

满脸问号,一头雾水。查找了一大圈, 我终于悟了。

js中的堆最简单就是直接把数组当成堆, 通过索引去关联自己的子项:

比如索引[0]的元素,它的左右两个子项分别是索引[1]、 索引[2]的元素;

然后索引[1]的子项是索引[3]、索引[4]

转换公式为: 索引i的子项等于 2i + 12i + 2

什么是堆?

堆的预备知识

  • 堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点;

  • 完全二叉树: 二叉树除开最后一层,其他层结点数都达到最大,最后一层的所有结点都集中在左边(左边结点排列满的情况下,右边才能缺失结点);

    完全二叉树图示如下:

    f9dcd100baa1cd1171faf1bdb512c8fcc2ce2dda.jpg

    新增元素一定是从左往右依次添加!!!

  • 其中堆又分为大顶堆和小顶堆。大顶堆意味着从上往下,父节点的值永远大于子节点。小顶堆则反过来。

大顶堆和小顶堆图示如下:

u=3819796237,1305402653&fm=26&fmt=auto.webp

如何实现一个堆排序?

堆排序有两个核心方法:

初始化方法,一开始要对倒数第二层开始,每个节点确保是大于自己的子节点的,如果不大于就交换位置;一层层往上递进直到根节点为止。

插入元素的时候,我们直接插入到头部, 然后当它的值大于它的父元素时,不断与父元素交换位置冒泡下去,直到达到底层节点为止。如果节点已经有序了,那么我们可以把它跟最后的末尾节点交换位置,然后对新的头节点做同样的操作,重复N轮即可对整个数组排序。

堆排序的实现

// 堆排序
function heapSort(arr) {
    buildMaxHeap(arr);
    const n = arr.length - 1;
    
    // 把当前的值放到最后面,然后把最后面的值作为头节点
    // 然后调整堆的顺序即可
    for (let i = n; i > 0; i--) {
        swap(arr, 0, i);
        heapify(arr, i - 1);
    }
}

// 交换节点的值
function swap(arr, i, j) {
    const temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

// 建立最大堆,初始化数据的时候操作
function buildMaxHeap(arr) {
    const n = arr.length - 1;
    // 最后一个数的父节点是(n - 1) / 2 | 0;
    const cur = (n - 1) / 2 | 0;

    // 从倒数第二层开始排,最后一层不用调整;
    for (let i = cur; i >= 0; i--) {
        heapify(arr, n, i);
    }
}

// 调整堆顶元素所在的位置, 如果比下层元素小就往下交换位置
function heapify(arr, len, i = 0) {
    let left = i * 2 + 1;
    let right = i * 2 + 2;

    let maxIndex = i;

    // 左节点是最大值的话
    if (left <= len && arr[left] > arr[maxIndex]) {
        maxIndex = left;
    }

    // 右节点是最大值的话
    if (right <= len && arr[right] > arr[maxIndex]) {
        maxIndex = right;
    }

    // 如果当前节点不是最大值,那么当前节点的值要往下传递,让下面值大的冒泡上来
    if (maxIndex !== i) {
        swap(arr, i, maxIndex);
        heapify(arr, len, maxIndex);
    }
}

堆排序的使用场景 -- 优先队列

在我们常用的数据结构中,有队列和栈这两个概念,其中队列是先进先出,栈是先进后出,那么当我们想按照我们指定的顺序,不管先进后进,优先级高的先出的时候,那么就可以用优先队列来实现了。比如说我们做一个调度功能,有三条指令待下发,分别是[ 选择制冷模式 、 开启空调、 温度调为20℃ ];正常来讲肯定是要先开启空调才去做其他两个动作的,所以我们可以给每个指令身上加上一个属性:sortNum让他手动进行排序。常规操作我们得对所有数据做一个排序,然后每次取出优先级最高的那一条。但是实际上我们只需要保证每次能拿到优先级最高的元素,其他元素的排列顺序我们并不关心,这时候就可以使用优先队列来实现。因为我们知道,在堆排序中父节点的优先级总是大于子节点的优先级。

堆排序和优先队列的区别

堆排序是一种排序方式,相当于我们的Array.sort这种方法,可以拿来操作数组改变它的元素位置;

优先队列是一种数据结构,是基于堆排序实现的,相当于一个特殊的Array,只不过它指定了自己的排序方式必须是堆排序。

堆排序和优先队列实战

由于这部分的篇幅过长,我单独另起了一篇文章来记录[路飞]_一起刷leetcode 692. 前K个高频单词

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。