数据结构——堆

1. 堆的概念

堆是一种特殊的树,满足如下条件:

  1. 完全二叉树:除了最后一层,其他层节点个数都是满的,最后一层的节点都集中在左侧连续位置
  1. 堆中每一个节点的值都必须大于等于(或小于等于)其左右节点的值

    • 每个节点都大于等于其左右节点的叫大根堆
    • 每个节点都小于等于其左右节点的叫小根堆

堆和二叉搜索树的区别是:二叉搜索树的要求更为严格,它要求某个节点大于(小于)左侧节点小于(大于)右侧节点,而堆只要求某个节点大于(小于)左右两侧节点即可,因此同一组数据可以构建多种不同形态的堆

2. 堆的表示

堆是完全二叉树,因此大部分时候使用数组来存储堆。

如上图,我们使用len来表示数组长度,i表示当前节点,我们可以总结出如下规律:

节点 索引
根节点root 0
当前节点current i
父节点parent floor((i - 1) / 2)
左孩子节点left i * 2 + 1
右孩子节点right i * 2 + 2
最后一个孩子节点

3. 堆的构建

3.1 堆化

堆化指的是对数组元素顺序进行调整,以满足堆的特性,这个调整过程叫做堆化。

堆化分两种:

  • 从下往上(上浮)

    当前元素不断向上和父节点比较大小:

    • 大顶堆:当前元素比父节点大,交换,让大的节点上去
    • 小顶堆:当前元素比父节点小,交换,让小的节点上去
  • 从上往下(下沉)

    当前元素不断向下和两个孩子节点比较大小

    • 大顶堆:当前元素与子节点中较大的比,比子节点小交换,让小的节点下去
    • 小顶堆:当前元素与子节点中较小的比,比子节点大交换,让大的节点下去

3.2 构建堆的思路

通过上小节我们了解到,堆化是对有孩子节点的节点进行的操作,因此我们只需要对二叉树的每个非孩子节点进行堆化即可得到堆。根据2.堆的表示那节总结的规律可知最后一个非叶子节点的索引为Math.floor(len / 2) - 1,因此我们对需要对数组从此索引开始向0遍历,依次对节点进行堆化即可得到一个堆。

4. 堆的代码实现

这里以大根堆为例,下面我们通过代码实现一个简单的大根堆类,其中包括堆的初始化,插入元素,删除堆顶元素这三个功能。

kotlin 复制代码
 class Heap {
   constructor(arr) {
     this.arr = arr;
     this.heapify();
   }
   /**
    * 堆化
    */
   heapify() {
     for (let i = Math.floor(this.arr.length / 2) - 1; i >= 0; i--) {
       this.sink(this.arr, i);
     }
   }
   /**
    *
    * @param {Array} arr
    * @param {Number} i
    * arr[i]节点的下沉操作,即自顶向下堆化
    */
   sink(arr, i) {
     // 当左孩子节点的索引合规
     while (2 * i + 1 < arr.length) {
       let j = 2 * i + 1; // 初始化将最大孩子节点指针指向左孩子
       if (j + 1 < arr.length && arr[j + 1] > arr[j]) {
         // 如果当前节点的右孩子比左孩子更大 最大孩子节点指针指向右孩子
         j++;
       }
       if (arr[i] > arr[j]) {
         // 当前节点的左右儿子都 <= 当前节点,停止下沉
         break;
       }
       // 下沉------交换值
       [arr[i], arr[j]] = [arr[j], arr[i]];
       // 下沉------更新当前操作的节点i
       i = j;
     }
   }
   /**
    *
    * @param {Number} val
    * 向堆中插入值为val的元素,val插入数组最后一位,自底向上堆化
    */
   insert(val) {
     this.arr.push(val);
     let i = this.arr.length - 1; // 初始化当前操作的节点 即插入节点的索引
     while (i > 0) {
       let j = Math.floor((i - 1) / 2); // 父节点的索引
       if (this.arr[i] <= this.arr[j]) {
         // 当前节点小于等于父节点 停止上浮
         break;
       }
       // 上浮------交换
       [this.arr[i], this.arr[j]] = [this.arr[j], this.arr[i]];
       // 上浮------更新当前操作的节点i
       i = j;
     }
   }
   /**
    *
    * 删除堆顶元素
    */
   delete() {
     let len = this.arr.length;
     // 将最后一个元素值和堆顶互换
     [this.arr[len - 1], this.arr[0]] = [this.arr[0], this.arr[len - 1]];
     // 删除最后一个元素
     this.arr.pop();
     // 下沉
     this.sink(this.arr, 0);
   }
 }

验证结果:

scss 复制代码
 let heap = new Heap([1, 6, 5, 6, 88, 3, 5, 10]);
 console.log(heap.arr);  // [88, 10, 5, 6, 6, 3, 5, 1]
 heap.insert(100);
 console.log(heap.arr);  // [100, 88, 5, 10, 6, 3, 5, 1, 6]
 heap.delete();
 heap.delete();
 heap.delete();
 heap.delete();
 console.log(heap.arr);  // [ 6, 5, 5, 1, 3 ]

根据结果可以看到完全符合大根堆的要求。

参考:

  1. 数据结构与算法之 ------ 堆(Heap)和堆排序
  2. LeetCode|如何建立一个堆|大顶堆|小顶堆|必会
相关推荐
zhougl9961 小时前
html处理Base文件流
linux·前端·html
花花鱼1 小时前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_1 小时前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo3 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
TDengine (老段)3 小时前
TDengine 中的关联查询
大数据·javascript·网络·物联网·时序数据库·tdengine·iotdb
杉之4 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端4 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡4 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木6 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!6 小时前
优选算法系列(5.位运算)
java·前端·c++·算法