堆知识小窥

84 阅读3分钟

堆知识小窥

1. 堆的概念

堆是基于完全二叉树的一种数据结构,就是连续的一片存储空间。

插入知识 深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树

2. 堆的分类

堆可以分为大顶堆和小顶堆。

  1. 大顶堆:任意一个节点都比他的子节点大。所以大顶堆的最大值就是根节点
  2. 小顶堆:任意一个节点都比他的子节点小。所以小顶堆的最小值就是根节点

3. 堆的操作

堆的操作分为插入和弹出

  1. 插入
    • 大顶堆:当前插入的节点和父节点进行比较,如果当前插入的节点比父节点大,那么调换他们的位置,以此类推
    • 小顶堆:当前插入的节点和父节点进行比较,如果当前插入的节点比父节点小,那么调换他们的位置,以此类推
  2. 弹出
    • 大顶堆:将最后一个节点放在根节点,然后将新的根节点和子节点进行比较,将三个节点总最大的放到根节点,如果产生交换,那么将交换过的原根节点,也就是现在的新子节点,然后将新子节点作为新的根节点,然后和新的根节点的子节点进行上述操作的递归处理
    • 小顶堆:将最后一个节点放在根节点,然后将新的根节点和子节点进行比较,将三个节点总最小的放到根节点,如果产生交换,那么将交换过的原根节点,也就是现在的新子节点,然后将新子节点作为新的根节点,然后和新的根节点的子节点进行上述操作的递归处理

4. 堆的代码实现

  • 大顶堆
  class BigHeap {
    constructor () {
      // 用于存储堆中的数据
      this.arr = []
      // 堆中的元素的数量
      this.count = 0
    }

    // 交换arr中的两个数据
    swap (a, b) {
      const temp = this.arr[a]
      this.arr[a] = this.arr[b]
      this.arr[b] = temp
    }

    // 将元素放入堆中
    push (val) {
      this.arr.push(val)
      this.count++
      let index = this.count - 1
      // 找到父元素,然后和父元素进行比较,如果比父元素大
      while (index && this.arr[Math.floor((index - 1) / 2)] < this.arr[index]) {
        this.swap(Math.floor((index - 1) / 2), index)
        index = Math.floor((index - 1) / 2)
      }
    }

    // 将堆顶元素推出
    pop () {
      if (this.count === 0) return null
      const res = this.top()
      // 将最后一个元素放置到首位置
      this.arr[0] = this.arr.pop()
      this.count--
      let index = 0
      // 堆中的最大下标
      const count = this.count - 1
      // 表明 index 有子节点
      while (index * 2 + 1 <= count) {
        // temp 用来存储以 index 作为根节点所形成的三角区域中最大的元素的下标
        let temp = index
        if (this.arr[index * 2 + 1] > this.arr[index]) {
          // 更新 temp
          temp = index * 2 + 1
        }
        if (
          count >= index * 2 + 2 &&
          this.arr[temp] < this.arr[index * 2 + 2]
        ) {
          // 更新 temp
          temp = index * 2 + 2
        }
        // 如果以 index 作为根节点所形成的三角区域中最大的元素就是 index ,则没有必要往下继续比较
        if (temp === index) break

        this.swap(index, temp)
        // 继续以新的 index 作为根节点,然后比较三角区域
        index = temp
      }
      return res
    }

    // 返回堆顶元素
    top () {
      if (this.count === 0) return null
      return this.arr[0]
    }
  }
  const big = new BigHeap()
  big.push(3)
  big.push(4)
  big.push(5)
  big.push(6)
  console.log(big.pop())
  console.log(big.pop())
  console.log(big.pop())
  console.log(big.pop())
  • 小顶堆
 class SmallHeap {
    constructor () {
      // 用于存储堆中的数据
      this.arr = []
      // 堆中的元素的数量
      this.count = 0
    }

    // 交换arr中的两个数据
    swap (a, b) {
      const temp = this.arr[a]
      this.arr[a] = this.arr[b]
      this.arr[b] = temp
    }

    // 将元素放入堆中
    push (val) {
      this.arr.push(val)
      this.count++
      let index = this.count - 1
      // 比较和父元素的大小,如果比父元素小
      while (index && this.arr[Math.floor((index - 1) / 2)] > this.arr[index]) {
        this.swap(Math.floor((index - 1) / 2), index)
        index = Math.floor((index - 1) / 2)
      }
    }

    // 将堆顶元素推出
    pop () {
      if (this.count === 0) return null
      const res = this.top()
      // 将最后一个元素放置到首位置
      this.arr[0] = this.arr.pop()
      this.count--
      let index = 0
      // 堆中的最大下标
      const count = this.count - 1
      // 表明 index 有子节点
      while (index * 2 + 1 <= count) {
        // temp 用来存储以 index 作为根节点所形成的三角区域中最小的元素的下标
        let temp = index
        if (this.arr[index * 2 + 1] < this.arr[index]) {
          // 更新 temp
          temp = index * 2 + 1
        }
        if (
          count >= index * 2 + 2 &&
          this.arr[temp] > this.arr[index * 2 + 2]
        ) {
          // 更新 temp
          temp = index * 2 + 2
        }
        // 如果以 index 作为根节点所形成的三角区域中最大的元素就是 index ,则没有必要往下继续比较
        if (temp === index) break

        this.swap(index, temp)
        // 继续以新的 index 作为根节点,然后比较三角区域
        index = temp
      }
      return res
    }

    // 返回堆顶元素
    top () {
      if (this.count === 0) return null
      return this.arr[0]
    }
  }
  const small = new SmallHeap()
  small.push(3)
  small.push(4)
  small.push(6)
  small.push(5)
  small.push(2)
  console.log(small.pop())
  console.log(small.pop())
  console.log(small.pop())
  console.log(small.pop())
  console.log(small.pop())

5. 堆的应用场景

堆适合处理的场景:在一个集合中寻找最值。比如在一个数组中寻找最大值或者最小值

6. 堆的设计改进

上述设计的无论是大顶堆还是小顶堆,默认节点元素是数字,那么我们能不能将元素扩展成你需要的类型?看一下以上实现,如果要是扩展的话,那么应该是在比较大小的时候需要改进一下,把我们需要比较的函数传入即可。则: