# 2.5 堆

堆的一个经典的实现是完全二叉树。这样实现的堆称为二叉堆

完全二叉树:增加了限定条件的二叉树。假设一个二叉树的深度为 n。为了满足完全二叉树的要求,该二叉树的前n - 1 层必须填满,第 n 层的所有的节点都连续集中在最左边。

这种数据结构具有以下两个性质:

  • 堆总是一棵完全二叉树。即除了最底层,其他层的节点都被元素填满,且最底层从左到右填入

  • 任意节点小于(或大于)它的所有子节点

将根节点最大的堆叫做最大堆大根堆,根节点最小的堆叫做最小堆小根堆

xcooo xcooo

# 2.5.1 堆和二叉搜索树的区别

  1. 节点的顺序

    在二叉搜索树中,左子节点必须比父节点小,右子节点必须必比父节点大。但是在堆中并非如此。在最大堆中两个子节点都必须比父节点小,而在最小堆中,它们都必须比父节点大。

  2. 搜索

    在二叉树中搜索会很快,但是在堆中搜索会很慢。在堆中搜索不是第一优先级,因为使用堆的目的是将最大(或者最小)的节点放在最前面,从而快速的进行插入、删除操作

# 2.5.2 一棵树的数组对象

xcooo
[ 15, 10, 9, 7, 5, 3 ]
1

节点位置计算公式:

parent(i) = Math.floor((i - 1)/2);
left(i)   = 2i + 1;
right(i)  = 2i + 2;
1
2
3
Node Index 父节点 左子节点 右子节点
15 0 -1 1 2
10 1 0 3 4
9 2 0 5
7 3 1
5 4 1
3 5 2

# 2.5.3 堆的操作

插入操作

xcooo

删除根节点

xcooo

堆有两个核心的操作,分别是 shiftUpshiftDown 。前者用于添加元素,后者用于删除根节点。

shiftUp 的核心思路是一路将节点与父节点对比大小,如果比父节点大,就和父节点交换位置。

shiftDown 的核心思路是先将根节点和末尾交换位置,然后移除末尾元素。接下来循环判断父节点和两个子节点的大小,如果子节点大,就把最大的子节点和父节点交换。

# 2.5.4 最大堆的实现

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

  insert(item) {
    // 将节点直接入队
    this.heap.push(item);
    // 判定是否需要交换位置
    this.shiftUp(this.getSize() - 1);
  }

  remove(index) {
    // 交换位置并且删除队尾节点
    this.swap(index, this.getSize() - 1);
    this.heap.splice(this.getSize() - 1, 1);
    // 判定是否需要交换位置
    this.shiftDown(index);
  }

  getParentIndex(index) {
    return Math.floor((index - 1) / 2);
  }

  getLeftIndex(index) {
    return index * 2 + 1;
  }

  shiftUp(index) {
    // 如果当前节点比父节点大,就交换
    while(this.heap[index] > this.heap[this.getParentIndex(index)]) {
      this.swap(index, this.getParentIndex(index));
      // 将索引修改成父节点
      index = this.getParentIndex(index);
    }
  }

  shiftDown(index) {
    // 判断当前节点是否有左子节点
    while(this.getLeftIndex(index) < this.getSize()) {
      let leftIndex = this.getLeftIndex(index);
      let rightIndex = leftIndex + 1;
      let changeIndex = leftIndex;
      // 判断是否有右子节点,并且右子节点是否大于左子节点
      if (rightIndex < this.getSize() && this.heap[rightIndex] > this.heap[leftIndex]) {
        changeIndex = rightIndex;
      }
      // 判断父节点是否比子节点都大,则终止循环,否则就交换位置
      this.swap(index, changeIndex);
      index = changeIndex;
    }
  }

  swap(left, right) {
    const rightValue = this.heap[right];
    this.heap[right] = this.heap[left];
    this.heap[left] = rightValue;
  }

  getSize() {
    return this.heap.length;
  }

  isEmpty() {
    return this.getSize() === 0;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
上次更新: 2021/2/24 上午10:55:31