数据结构——堆

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|如何建立一个堆|大顶堆|小顶堆|必会
相关推荐
冠位观测者3 小时前
【Leetcode 热题 100】208. 实现 Trie (前缀树)
数据结构·算法·leetcode
蟾宫曲4 小时前
在 Vue3 项目中实现计时器组件的使用(Vite+Vue3+Node+npm+Element-plus,附测试代码)
前端·npm·vue3·vite·element-plus·计时器
秋雨凉人心4 小时前
简单发布一个npm包
前端·javascript·webpack·npm·node.js
liuxin334455664 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
qq13267029404 小时前
运行Zr.Admin项目(前端)
前端·vue2·zradmin前端·zradmin vue·运行zradmin·vue2版本zradmin
魏时烟5 小时前
css文字折行以及双端对齐实现方式
前端·css
哥谭居民00016 小时前
将一个组件的propName属性与父组件中的variable变量进行双向绑定的vue3(组件传值)
javascript·vue.js·typescript·npm·node.js·css3
踢足球的,程序猿6 小时前
Android native+html5的混合开发
javascript
2401_882726486 小时前
低代码配置式组态软件-BY组态
前端·物联网·低代码·前端框架·编辑器·web
kittygilr6 小时前
matlab中的cell
开发语言·数据结构·matlab