数据结构 实现堆

本文介绍了堆的两种类型——最大堆和最小堆,以及其作为优先队列的别名。讨论了堆在求解topK问题和堆排序中的应用,并提供了一个堆的简单实现。
摘要由CSDN通过智能技术生成

堆分类:最大堆、最小堆
堆别名:优先队列
堆应用:求topK,堆排序

  1. 堆模板

    function Heap(_arr, _compareFunc){
      // 私有属性
      let heap = [];
      // 对象公有属性
      this.size = 0;
    
      // 私有方法
      // 计算左、右、父节点索引
      let left = () => {};
      let right = () => {};
      let parent = () => {};
      // 堆的shiftUp、shiftDown操作
      let shiftUp = () => {};
      let shiftDown = () => {};
      // 建堆函数
      let createHeap = () => {};
      // 对象公有方法
      this.insert = () => {};   // 插入新元素
      this.pop = () => {};      // 堆顶出堆
    	
      // 初始化操作
      console.error('未进行堆初始化');
      // 属性初始化
      // 建堆
    }
    
  2. 堆简单实现

    function Heap(_arr, _compareFunc){
      /**
       * 堆
       * @param {Array} _arr: 数组,注意传的是引用,建堆、insert、pop操作都会改变原数组
       * @param {Function} _compareFunc: 比较函数,参数(a, b)
       *                  元素为Number的最大堆,(a, b) => {return a-b;}
       *                  元素为Number的最小堆,(a, b) => {return b-a;}
       */
      // 私有属性
      let heap = [];
    	
      // 对象公有属性
      this.size = 0;
    
      // 计算左、右、父节点
      let left = (_i) => {
        return 2 * _i + 1;
      }
      let right = (_i) => {
        return 2 * _i + 2;
      }
      let parent = (_i) => {
        if(_i % 2 === 1) return (_i - 1) / 2;
        return (_i - 2) / 2;
      }
    
      // 私有函数
      let shiftUp = (_i) => {
        /**
         * 堆的元素shiftUp操作
         * 步骤:
         * 1. 判断当前结点与其父结点的位置是否正确,不正确的话则交换两各结点
         * 2. 结点交换到父结点位置后继续递归进行shiftUp操作,直到位置正确或者到达堆顶
         * @param {Number} _i: 要进行shiftUp的元素的索引
         */
        let p = parent(_i);
        if(p >= 0){
          if(_compareFunc(heap[_i], heap[p]) > 0){
            [heap[_i], heap[p]] = [heap[p], heap[_i]]
          }
          shiftUp(p);
        }
      }
      let shiftDown = (_i) => {
        /**
         * 堆的元素shiftDown操作
         * 步骤:
         * 1. 当前结点与两个子节点中值最大(小)的结点比较,若当前结点位置不正确,则与子结点交换
         * 2. 交换到子结点后继续递归进行shiftDown操作,直到其位置正确或到达叶节点
         * @param {Number} _i: 要进行shiftDown的元素的索引
         */
        let l = left(_i), r = right(_i);
        if(l < this.size && r < this.size){ // 左右孩子都存在
          let index = _compareFunc(heap[r], heap[l]) < 0 ? l: r;
          if (_compareFunc(heap[index], heap[_i]) > 0){
            [heap[_i], heap[index]] = [heap[index], heap[_i]];
            shiftDown(index);
          }
        }
        else if(l < this.size){ // 只有左孩子
          if (_compareFunc(heap[l], heap[_i]) > 0) {
            [heap[_i], heap[l]] = [heap[l], heap[_i]];
            shiftDown(l);
          }
        }
        // 叶节点(完全二叉树,没有只有右孩子的情况)
      }
      let createHeap = (_root) => {
        /**
         * 建堆函数
         * 原理:假设当前结点的两个子树都是堆,那么对当前结点做shiftDown后,以当前为根结点的完全二叉树也完成了建堆
         * 步骤:后续遍历二叉树,递归对两个子树进行建堆,再对当前结点进行
         */
        let l = left(_root), r = right(_root);
        if(l < this.size){
          createHeap(l);
          if(r < this.size){  // 有左孩子的前提下才有可能有右孩子
            createHeap(r);
          }
          shiftDown(_root);
        }
      }
    
      // 对象公有方法
      this.insert = (_element) => {
        /**
         * 往堆中插入结点
         * 步骤:插入到堆尾,进行shiftUp
         */
        heap.push(_element);
        this.size++;
        shiftUp(this.size - 1);
      }
    
      this.pop = () => {
        /**
         * 堆顶结点出堆
         * 步骤:元素出堆,从堆尾取一元素放到堆顶,进行shiftDown
         */
        if(this.size > 0) {
          let head = heap[0];
          this.size--;
          let tail = heap.pop();
          if (this.size > 0) {
            heap[0] = tail;
            shiftDown(0);
          }
          return head;
        }
      }
    
      // 初始化
      heap = _arr;
      this.size = _arr.length;
      createHeap(0);	// 建堆
    
      // 测试用
      this.test = (_root) => {
        // 先序遍历打印树结构,用于测试
        if(_root < this.size){
          console.log(heap[_root]);
          this.test(left(_root));
          this.test(right(_root));
        }
        else console.log('null')
      }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值