算法 系列 二叉堆

基础数据结构...

Posted by lichao modified on September 20, 2019

概念

二叉堆是一种特殊的堆。具有如下的特性:

  • 具有完全二叉树的特性。
  • 堆中的任何一个父节点的值都大于等于它左右孩子节点的值,或者都小于等于它左右孩子节点的值。

其主要操作就两个, sink (下沉)和 swim (上浮),⽤以维护⼆叉堆的性质。其主要应⽤有两个,⾸先是⼀种排序⽅法「堆排序」,第⼆是⼀种很有⽤的数据结构「优先级队列」。

根据第二条特性,我们又可以把二叉堆分成两类:

  1. 最大堆: 父节点的值大于等于左右孩子节点的值
  2. 最小堆: 父节点的值小于等于左右孩子节点的值 algorithm

⼆叉堆其实就是⼀种特殊的⼆叉树(完全⼆叉树),只不过存储在数组⾥。⼀般的链表⼆叉树,我们操作节点的指针,⽽在数组⾥,我们把数组索引作为指针

实现

存储结构是数组,将数组维护成逻辑上的完全二叉树。在二叉堆中插入和删除、查找的时间复杂度都是log(n)

构建堆

algorithm

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
    public static void buildMaxHeap(int[] array) {

        if (array == null || array.length == 1)
            return;
        // 堆的公式 是 int root = i, int left = 2*i+1, int right = 2*i+2;
        // 获取第一个非叶子节点
        int cursor = (array.length / 2) - 1;
        for (int i = cursor; i >= 0; i--) { // 这样for循环下,就可以第一次排序完成
            // maxHeap(array, array.length, i);
            minHeap(array, array.length, i);
        }
    }

    // 最小堆
    public static void minHeap(int[] array, int heapSieze, int index) {
        int left = index * 2 + 1; // 左子节点
        int right = index * 2 + 2; // 右子节点
        int maxValue = index; // 暂时定在Index的位置就是最小值

        // 如果左子节点的值,比当前最小的值小,就把最小值的位置换成左子节点的位置
        if (left < heapSieze && array[left] < array[maxValue]) {
            maxValue = left;
        }

        //  如果右子节点的值,比当前最小的值小,就把最小值的位置换成左子节点的位置
        if (right < heapSieze && array[right] < array[maxValue]) {
            maxValue = right;
        }

        // 如果不相等,说明这个子节点的值有比自己小的,位置发生了交换了位置
        if (maxValue != index) {
            swap(array, index, maxValue); // 就要-交换位置元素

            // 交换完位置后还需要判断子节点是否打破了最小堆的性质。最小性质:两个子节点都比父节点大。
            minHeap(array, heapSieze, maxValue);
        }
    }

    // 数组元素交换
    public static void swap(int[] array, int index1, int index2) {
        int temp = array[index1];
        array[index1] = array[index2];
        array[index2] = temp;
    }

堆排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   public static void heapSort(int[] array) {
        if (array == null || array.length == 1)
            return;

        buildMaxHeap(array); // 第一次排序,构建最大堆,只保证了堆顶元素是数组里最大的

        for (int i = array.length - 1; i >= 1; i --) {
            // 经过上面的一些列操作,目前array[0]是当前数组里最大的元素,需要和末尾的元素交换
            // 然后,拿出最大的元素
            swap(array, 0, i);

            // 交换完后,下次遍历的时候,就应该跳过最后一个元素,也就是最大的那个值,然后开始重新构建最大堆
            // 堆的大小就减去1,然后从0的位置开始最大堆
            // maxHeap(array, i, 0);
            minHeap(array, i, 0);
        }
    }

插入

插入位置是完全二叉树的最后一个位置。在插入结点后需要对堆进行调整。调整方法为(对于小顶堆而言):将插入的结点与其父结点比较,若小于其父结点的值,则交换两者。重复此操作,直至该结点不比其父结点小,或者该结点成为根结点。可以通过插入结点到一个已经存在的堆中,也可以通过不断插入结点来构建一个堆。 algorithm

删除

删除节点一般删除的是根节点。把根节点删除之后,用二叉堆的最后一个元素顶替上来,然后在进行调整恢复。

应用场景

优先级队列

优先级队列这种数据结构有⼀个很有⽤的功能,当插⼊或者删除元素的时候,元素会⾃动排序,这底层的原理就是⼆叉堆的操作。

数据结构的功能⽆⾮增删改查,优先级队列有两个主要 API,分别是 insert 插⼊⼀个元素和 delMax 删除最⼤元素(如果底层⽤最⼩堆,那么就是 delMin )。