🥳前端算法面试之堆排序二-每日一练

上篇文章分享了堆排序的步骤,第一步建堆,第二步堆化,也就是堆调整。这篇文章分享的内容是建堆的第二种方式,以及堆节点的删除,以及复杂度分析

想要把算法学好,数据结构的基础是必不可少的

第一种建堆的方式是借用了插入排序的思想,将数组分成了两个部分,第一部分是已经建好了的堆,第二部分是等待建堆的元素。建堆的过程是不断地将第二部分的元素放入到第一部分中。这种方式的复杂度是 nlogn

下面是上篇文章中的代码实现,详细了解可以移步:🥳前端算法面试之堆排序-每日一练

javascript 复制代码
class Heap {
  constructor(data) {
    this.data = data;
  }

  build() {
    for (let i = 2; i < this.data.length; i++) {
      this.heapfyTop(i);
    }
  }
  heapfyTop(n) {
    while (n > 1 && this.data[n] > this.data[Math.floor(n / 2)]) {
      this.swap(n, Math.floor(n / 2));
      n = Math.floor(n / 2);
    }
  }
  swap(index1, index2) {
    const temp = this.data[index1];
    this.data[index1] = this.data[index2];
    this.data[index2] = temp;
  }
}

第二种建堆方式

第二种建堆的方式复杂度更加低。

  • 首先直接将数组看成是一个完整的部分,一个堆。建堆的任务是将这个堆调整成大根堆。
  • 然后从第一个非叶子节点开始,将这个看成是一个小的堆,并且往下检查这个小堆是否符合大根堆的定义,即如果有子节点大于父节点,就更换两者的位置。
  • 检查剩下的非叶子节点,重复上面第二步的操作。

当所有的叶子都检查完了,完整的大根堆也就建好了。这种建堆的方式的时间复杂度是 n。下面看代码实现

javascript 复制代码
class Heap{
  //省略其他代码
  build2() {
    for (let i = Math.floor(this.data.length / 2); i > 0; i--) {
      this.heapfyBelow(i, this.data.length);
    }
  }
  heapfyBelow(n, end) {
    // 是否是叶子节点
    while (n * 2 <= end) {
      let maxIndex = n;
      // 是否有左孩子
      if (n * 2 <= end && this.data[maxIndex] < this.data[n * 2]) maxIndex = n * 2;
      // 是否有右孩子
      if (n * 2 + 1 <= end && this.data[maxIndex] < this.data[n * 2 + 1]) maxIndex = n * 2 + 1;
      if (maxIndex == n) break;
      this.swap(n, maxIndex);
      n = maxIndex;
    }
  }
}

build2 就是第二种建堆的方法。从第一个非叶子开始遍历Math.floor(this.data.length / 2),因为是完全二叉树,当树的节点为 n,那么第一个非叶子节点的下标就是 n/2 向下取整。

调整小堆的方法heapfyBelow,接收两个参数,第一个参数 n 表示从哪个下标开始检查,第二个参数表示检查到哪里结束。因为整个数组都看成一个完整的堆,所以 end 一直是this.data.lengthheapfyBelow的基本逻辑:看左右孩子是否大于当前节点,如果有就选一个最大的子节点,并与其更换位置。一直往下检查,直到最后一个非叶子节点。

测试代码:

javascript 复制代码
const data = [-1, 21, 33, 5, 42, 123, 54, 65, 23, 33, 55];
const heap = new Heap(data);

heap.build2();

console.log(heap.data);
// [
//   -1, 123, 55, 65, 42,
//   33,  54,  5, 23, 33,
//   21
// ]

结合完全二叉树的数组存放性质,可以看到上面的输出结果是完全符合大根堆定义的。

节点的删除

大根堆一般会被用来实现优先级队列,即无论放进去的次数,选出元素的时候总是选排在最前面的。像 react 中的优先级任务队列,总是会选择过期时间最长的任务来执行。而大根堆实现的任务队列总是会让过期时间最长的任务放在数组的最前面。

删除过程也很简单,直接将堆顶的节点替换成堆的最后一个节点,然后重新调整堆,以保持大根堆的性质

javascript 复制代码
class Heap{
  // 省略其他代码
  /**
* 删除堆中的最大值(根节点)
* @returns {number} 删除的值
*/
  pop() {
    // 获取待删除的根节点
    const data = this.data[1];

    // 将堆的最后一个节点替换为根节点
    this.data[1] = this.data.pop();

    // 重新调整堆,使其保持大根堆的性质
    this.heapfyBelow(1, this.data.length);

    // 返回删除的值
    return data;
  }
}

代码很简单,就不解释了

测试代码:

javascript 复制代码
const data = [-1, 21, 33, 5, 42, 123, 54, 65, 23, 33, 55];
const heap = new Heap(data);

heap.build2();

console.log(heap.data);
// [
//   -1, 123, 55, 65, 42,
//   33,  54,  5, 23, 33,
//   21
// ]
console.log(heap.pop());
// 123
console.log(heap.data);
// [
//   -1, 65, 55, 54, 42,
//   33, 21,  5, 23, 33
// ]

堆顶的元素被弹出,剩余堆依旧符合大根堆的性质

复杂度分析

堆排序分成两步,第一步建堆的时间复杂度是 n,第二步持续堆调整的时间复杂度是 nlogn,所以整体的时间复杂度是 n+nlogn,省略较小的因子,所以最后的时间复杂度是 nlogn。

对于元素的插入,先放到堆的最后一个位置,然后再调整堆。所以时间复杂度是 1+logn,即 logn

对于元素的删除,先替换堆顶的元素,然后再调整堆,和插入时间复杂度一样,也是 1+logn,即 logn

在优先级队列的应用场景中,涉及到获取最优先的元素,以及元素的插入删除操作,堆的时间复杂度是比其他的数据结构都要更好

为什么是 logn,因为一棵完全二叉树的高度 h 和节点 n 有这样的关系:h = logn

总结

这篇文章紧接上篇文章:🥳前端算法面试之堆排序-每日一练,分享了建堆的第二种方式,以及堆节点的删除,以及复杂度分析。文章思路清晰,文笔清楚,代码详实,是个不可多得好文章啊

什么问题可以评论区留言哦。我每天都会分享一篇算法小练习,喜欢就点赞+关注吧

相关推荐
冠位观测者18 分钟前
【Leetcode 热题 100】208. 实现 Trie (前缀树)
数据结构·算法·leetcode
蟾宫曲1 小时前
在 Vue3 项目中实现计时器组件的使用(Vite+Vue3+Node+npm+Element-plus,附测试代码)
前端·npm·vue3·vite·element-plus·计时器
秋雨凉人心1 小时前
简单发布一个npm包
前端·javascript·webpack·npm·node.js
liuxin334455661 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
qq13267029401 小时前
运行Zr.Admin项目(前端)
前端·vue2·zradmin前端·zradmin vue·运行zradmin·vue2版本zradmin
小王爱吃月亮糖1 小时前
C++的23种设计模式
开发语言·c++·qt·算法·设计模式·ecmascript
魏时烟2 小时前
css文字折行以及双端对齐实现方式
前端·css
哥谭居民00013 小时前
将一个组件的propName属性与父组件中的variable变量进行双向绑定的vue3(组件传值)
javascript·vue.js·typescript·npm·node.js·css3
踢足球的,程序猿3 小时前
Android native+html5的混合开发
javascript
IT猿手3 小时前
最新高性能多目标优化算法:多目标麋鹿优化算法(MOEHO)求解LRMOP1-LRMOP6及工程应用---盘式制动器设计,提供完整MATLAB代码
开发语言·算法·matlab·智能优化算法·多目标算法