• ⑩ 数据结构之“堆”


    一、理论

    1. 堆简介

    • 堆是一种特殊的 完全二叉树
    • 所有的节点都大于等于(最大堆)或小于等于(最小堆)它的子节点

    1.1 js中的堆

    • js中通常用数组表示堆
    • 左侧子节点的位置是 2*index+1
    • 右侧子节点的位置是 2*index+2
    • 父节点位置是(index-1)/2

    1.2 应用

    • 堆能高效、快速找出最大值和最小值,时间复杂度O(1)
    • 找出第k个最大(小)问题

    2. js实现最小堆类

    2.1 实现步骤

    • 在类里声明一个数组,用来装元素
    • 主要方法:插入、删除堆顶、获取堆顶、获取堆大小
    class MinHeap {
      constructor() {
        this.heap = [];
      }
    }
    
    插入
    • 将值插入堆的底部,即数组尾部
    • 然后上移:将该值与父节点交换,知道父节点小于等于该值
    • 大小为k的堆中插入元素的时间复杂度为O(logk)
    class MinHeap {
      constructor() {
        this.heap = [];
      }
      swap(i1, i2) {
        const temp = this.heap[i1]
        this.heap[i1] = this.heap[i2]
        this.heap[i2] = temp
      }
      getParentIndex(i) {
        // return Math.floor((i-1)/2);
        return (i - 1) >> 1;
      }
      shiftUp(index) {
        if(index === 0) return;
        const parentIndex = this.getParentIndex(index);
        if(this.heap[parentIndex] > this.heap[index]) {
          this.swap(parentIndex, index);
          this.shiftUp(parentIndex);
        }
      }
      insert(value) {
        this.heap.push(value);
        this.shiftUp(this.heap.length-1);
      }
    }
    
    删除堆顶
    • 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构)
    • 然后下移:将新堆顶和它的子节点进行交换,直到子节点大于等于新堆顶
    • 大小为k的堆中删除堆顶的时间复杂度为O(logk)
    class MinHeap {
      constructor() {
        this.heap = [];
      }
      swap(i1, i2) {
        const temp = this.heap[i1]
        this.heap[i1] = this.heap[i2]
        this.heap[i2] = temp
      }
      getLeftIndex(i) {
        return i * 2 + 1
      }
      getRightIndex(i) {
        return i * 2 + 2
      }
      shiftDown(index) {
        const leftIndex = this.getLeftIndex(index)
        const rightIndex = this.getRightIndex(index)
        if(this.heap[leftIndex] < this.heap[index]) {
          this.swap(leftIndex, index)
          this.shiftDown(leftIndex)
        }
        if(this.heap[rightIndex] < this.heap[index]) {
          this.swap(rightIndex, index)
          this.shiftDown(rightIndex)
        }
      }
      pop() {
        this.heap[0] = this.heap.pop()
        this.shiftDown(0)
      }
    }
    
    获取堆顶和堆大小
    • 获取堆顶:返回数组的头部
    • 获取堆的大小:返回数组的长度
    class MinHeap {
      constructor() {
        this.heap = [];
      }
      peek() {
        return this.heap[0]
      }
      size() {
        return this.heap.length
      }
    }
    

    二、刷题

    1. 数组中的第k个最大元素(215)

    1.1 题目描述

    • 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素
    • 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素

    1.2 解题思路

    • 看到 第k个最大元素 --> 考虑选择使用最小堆

    1.3 解题步骤

    • 构建一个最小堆,并依次把数组的值插入堆中
    • 当堆的容量超过k,删除堆顶
    • 插入结束后,堆顶就是第k个最大元素
    function findKthLargest(nums, k) {
      const h = new MinHeap()
      nums.forEach(n => {
        h.insert(n)
        if(h.size() > k) {
          h.pop()
        }
      })
      return h.peek()
    }
    

    1.4 时间复杂度 && 空间复杂度

    • 时间复杂度:O(nlogk)
    • 空间复杂度:O(k)

    2. 前k个高频元素(347)

    2.1 题目描述

    • 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素
    • 可以按 任意顺序 返回答案
    直觉解法
    function topKFrequent(nums, k) {
      const map = new Map()
      nums.forEach(n => {
        map.set(n, map.has(n) ? map.get(n)+1 : 1)
      })
      const list = [...map].sort((a, b) => b[1] - a[1])
      return list.slice(0, k).map(n => n[0])
    }
    
    • 时间复杂度:O(n)
    • 空间复杂度:O(nlogn)

    2.2 解题思路

    输入: nums = [1,1,1,2,2,3], k = 2
    输出: [1,2]

    2.3 解题步骤

    function topKFrequent(nums, k) {
      const map = new Map()
      nums.forEach(n => {
        map.set(n, map.has(n) ? map.get(n)+1 : 1)
      })
      const h = new MinHeap()
      map.forEach((val, key) => {
        h.insert({ val, key })
        if(h.size() > k)  {
          h.pop()
        }
      })
      return h.heap.map(a => a.key)
    }
    

    2.4 时间复杂度 && 空间复杂度

    • 时间复杂度:O(nlogk)
    • 空间复杂度:O(n)

    3. 合并k个排序链表(23)

    3.1 题目描述

    • 给你一个链表数组,每个链表都已经按升序排列
    • 请你将所有链表合并到一个升序链表中,返回合并后的链表

    3.2 解题思路

    输入: lists = [ 1 -> 4 -> 5, 1 -> 3 -> 4, 2 -> 6 ]
    输出:[1,1,2,3,4,4,5,6]

    • 新链表的下一个节点一定是k个链表头中的最小节点 -> 考虑选择使用最小堆

    3.3 解题步骤

    • 构建最小堆,并依次把链表头插入堆中
    • 弹出堆顶接到输出链表,并将堆顶所在链表的新链表头插入堆中
    • 等堆元素全部弹出,合并工作就完成了
    function mergeKLists(lists) {
      const res = new ListNode(0)
      const h = new MinHeap()
      let p = res
      lists.forEach(l => {
        if(l) h.insert(l)
      })
      while(h.size()) {
        const n = h.pop()
        p.next = n
        p = p.next
        if(n.next) h.insert(n.next)
      }
      return res.next
    }
    

    3.4 时间复杂度 && 空间复杂度

    • 时间复杂度:O(nlogk)
    • 空间复杂度:O(k)

    三、总结 -- 技术要点

    • 堆是一种特殊的 完全二叉树

    • 所有的节点都大于等于(最大堆)或小于等于(最小堆)它的子节点

    • js通常用数组表示堆

    • 堆能高效、快速找出最大值和最小值,时间复杂度O(1)

    • 找出第k个最大(小)问题

  • 相关阅读:
    linux串口
    在demo板上用串口和AT指令调试GPRS模块
    发送短信
    html
    JavaScript
    frp
    sunke推荐
    ubus
    2021-8
    缓存一致性协议
  • 原文地址:https://www.cnblogs.com/pleaseAnswer/p/15847557.html
Copyright © 2020-2023  润新知