JavaScript实现最小堆

511 阅读1分钟

定义

二叉堆是一个完全二叉树,父节点与子节点要保持固定的序关系,并且每个节点的左子树和右子树都是一个二叉堆。
完全二叉树,即除最后一层节点外,其他层节点都必须要有两个子节点,并且最后一层节点都要左排列。

特性

  • 最大堆:根节点的键值是所有堆节点键值中最大者,且每个父节点的值都比子节点的值大
  • 最小堆:根节点的键值是所有堆节点键值中最小者,且每个父节点的值都比子节点的值小

思考点

首先需要实现一个 最小堆的class, 初始化一个数组, 用来存放最小堆。 同时, parent 和 child 的关系

  1. left child: i*2 +1
  2. right child: i*2 +2,
  3. parent: Math.floor((i -1) / 2)

最小堆封装

class MinHeap {
  constructor() {
    this.heap = [];
  }

  swap(i, j) {
    // [this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
    const temp = this.heap[i];
    this.heap[i] = this.heap[j];
    this.heap[j] = temp;
  }

  /**
   * @description 子元素获取父元素节点
   * @param {*} i
   * @return {*}
   */
  getParentIndex(i) {
    return (i - 1) >> 1;
  }

  /**
   * @description 父元素获取左节点
   * @param {*} i
   * @return {*}
   */
  getLeftIndex(i) {
    return i * 2 + 1;
  }

  /**
   * @description 父元素获取右节点
   * @param {*} i
   * @return {*}
   */
  getRightIndex(i) {
    return i * 2 + 2;
  }

  /**
   * @description 元素上移,一直找到最小元素位置
   * @param {*} index
   * @return {*}
   */
  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);
    }
  }

  /**
   * @description 元素下移, 当删除元素的时候, 需要用最后的元素覆盖根元素, 然后从上到下移动
   * @param {*} index
   */
  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)
    }
  }

  /**
   * @description 插入元素
   * @param {*} value
   */
  insert(value) {
    this.heap.push(value);
    this.shiftup(this.heap.length - 1);
  }

  /**
   * @description 删除元素
   */
  pop() {
    // 将最后一个元素挪到第一个元素,相当于删除第一个元素(最小堆的最小值)
    this.heap[0] = this.heap.pop();
    this.shiftDown(0);
  }

  /**
   * @description 获取根元素
   * @return {*}
   */
  peek() {
      return this.heap[0];
  }

  /**
   * @description 当前长度
   * @return {*}
   */
  size() {
      return this.heap.length;
  }
}

封装的另一种写法

class MinHeap {
  constructor(data = []) {
    // 最小堆
    this.data = data
  }
  // 计算长度
  size() {
    return this.data.length
  }
  // 比较
  compare(a, b) {
    return a - b
  }

  // 交换两个变量的值
  swap(index1, index2) {
    // [a, b] = [b, a]
    ;[this.data[index1], this.data[index2]] = [
      this.data[index2],
      this.data[index1],
    ]
  }

  // 获取最小堆的值
  peek() {
    return this.size() === 0 ? null : this.data[0]
  }

  // 添加元素
  push(node) {
    this.data.push(node)
    // 调整位置
    this.siftUp(node, this.size() - 1)
  }

  siftUp(node, i) {
    let index = i
    while (index > 0) {
      const parentIndex = (index - 1) >>> 1 // 除以2
      const parent = this.data[parentIndex]
      if (this.compare(node, parent) < 0) {
        // 子节点 < 父节点
        this.swap(index, parentIndex)
        index = parentIndex
      } else {
        break
      }
    }
  }

  // 删除最小堆的最小值
  pop() {
    if (this.size() === 0) {
      return null
    }

    const first = this.data[0]
    const last = this.data.pop()
    // if(first !== last) {
    if (this.size() !== 0) {
      this.data[0] = last
      // 向下调整
      this.siftDown(last, 0)
    }
  }

  siftDown(node, i) {
    let index = i
    const length = this.size()
    const halfLength = length >>> 1
    while (index < halfLength) {
      const leftIndex = (index + 1) * 2 - 1
      const rightIndex = leftIndex + 1
      const left = this.data[leftIndex]
      const right = this.data[rightIndex]
      if (this.compare(left, node) < 0) {
        // left < 父节点
        if (rightIndex < length && this.compare(right, left) < 0) {
          // right < left ,right 最小
          this.swap(rightIndex, index)
          index = rightIndex
        } else {
          // right >= left,left最小
          this.swap(leftIndex, index)
          index = leftIndex
        }
      } else if (rightIndex < length && this.compare(right, node) < 0) {
        // left > node, right < node
        // right 最小
        this.swap(rightIndex, index)
        index = rightIndex
      } else {
        // 根节点最小
        break
      }
    }
  }
}