堆的简介

堆是优先级队列的实现,是一种数据结构,你可以把它看做一棵完全二叉树。

 index:    1      2      3      4     5     6     7     8     9     10
         +-------------------------------------------------------------+
 array:  | 16  |  14  |  10  |  8  |  7  |  9  |  3  |  2  |  4  |  1  |
         +-------------------------------------------------------------+
                                |
                                |   可以把这个数组想像成二叉树
                                V

 index:    1               array:   16
          / \                       / \
         /   \                     /   \
        2     3                   14    10
       /\     /\                 /\     /\
      /  \   /  \               /  \   /  \
     4   5   6   7             8   7   9   3
    /\   |                    /\   |
   /  \  /                   /  \  /
  8   9 10                  2   4  1

堆的属性

堆结点的访问

通常堆是通过一维数组来实现的,树的根是第一个元素。

在数组起始位置为 1 的情形中(易于理解,如上图):

  • 父节点 i 的左子节点在位置 (2i)
  • 父节点 i 的右子节点在位置 (2i+1)
  • 子节点 i 的父节点在位置 floor(i/2)

在数组起始位置为 0 的情形中(用于代码中):

  • 父节点 i 的左子节点在位置 (2i+1)
  • 父节点 i 的右子节点在位置 (2i+2)
  • 子节点 i 的父节点在位置 floor((i-1)/2)

大根堆和小根堆

大根堆 (Max-heap)

  • 大根堆又称最大堆(大顶堆)
  • 最大堆中的最大元素值出现在根结点(堆顶)
  • 堆中每个父节点的元素值都大于等于其孩子结点(如果存在)

小根堆 (Min-heap)

  • 小根堆又称最小堆(小顶堆)
  • 最小堆中的最小元素值出现在根结点(堆顶)
  • 堆中每个父节点的元素值都小于等于其孩子结点(如果存在)

堆的操作

堆中定义以下几种操作:

  • 最大堆调整(Max Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
  • 创建最大堆(Build Max Heap):将堆中的所有数据重新排序
  • 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算

堆排序

我们假设数组起始位置为 1,方便理解。

Max-Heapify

Max-Heapify 是基本操作,我们会一遍又一遍的递归使用它。

例:对堆 A 的根结点(索引为 1 的结点)进行 Max-Heapify 操作Max-Heapify(A, 1)

       (4)
       / \        根结点是 4,它的孩子大于它,所以这个结点要与
      /   \       它的孩子结点中最大的元素 14 进行交换
    (14)   7
    /\     /
   /  \   /
  2   8   1

        14
       / \        交换过后的 4,继续看它的两个孩子是否满足条件
      /   \       发现不满足,这次它和孩子结点中最大的元素 8 进行交换
    (4)    7
    /\     /
   /  \   /
  2  (8)  1

        14
       / \        交换过后的 4 没有孩子,Max-Heapify 操作结束
      /   \
     8    7
    /\    /
   /  \  /
  2   4  1

Build-Max-Heap

Build-Max-Heap(A) 的操作如下 (n 是堆的大小)

for i=n/2 downTo 1:
    do Max-Heapify(A, i)

因为在 n/2 之后的结点是没有子结点的,所以只需要从 n/2 开始一直到 1 执行 Max-Heapify 即可。

Heap-Sort

堆排序的过程是:

  1. 创建一个堆
  2. 把堆首(最大值)和堆尾互换
  3. 把堆的尺寸缩小1
  4. 重复步骤2,直到堆的尺寸为1

Java 代码的实现如下1

import java.util.Arrays;

public class HeapSort {

    private int[] arr;

    public HeapSort(int[] arr){
        this.arr = arr;
    }

    /**
     * 堆排序的主要入口方法,共两步。
     */
    public void sort(){
        /*
         *  第一步:将数组堆化
         *  beginIndex = 第一个非叶子节点。
         *  从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。
         *  叶子节点可以看作已符合堆要求的节点,根节点就是它自己且自己以下值为最大。
         */
        int len = arr.length - 1;
        int beginIndex = (len - 1) >> 1;
        for(int i = beginIndex; i >= 0; i--){
            maxHeapify(i, len);
        }

        /*
         * 第二步:对堆化数据排序
         * 每次都是移出最顶层的根节点 A[0],与最尾部节点位置调换,同时遍历长度 - 1。
         * 然后从新整理被换到根节点的末尾元素,使其符合堆的特性。
         * 直至未排序的堆长度为 0。
         */
        for(int i = len; i > 0; i--){
            swap(0, i);
            maxHeapify(0, i - 1);
        }
    }

    private void swap(int i,int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    /**
     * 调整索引为 index 处的数据,使其符合堆的特性。
     *
     * @param index 需要堆化处理的数据的索引
     * @param len 未排序的堆(数组)的长度
     */
    private void maxHeapify(int index,int len){
        int li = (index << 1) + 1; // 左子节点索引
        int ri = li + 1;           // 右子节点索引
        int cMax = li;             // 子节点值最大索引,默认左子节点。

        if(li > len) return;       // 左子节点索引超出计算范围,直接返回。
        if(ri <= len && arr[ri] > arr[li]) // 先判断左右子节点,哪个较大。
            cMax = ri;
        if(arr[cMax] > arr[index]){
            swap(cMax, index);      // 如果父节点被子节点调换,
            maxHeapify(cMax, len);  // 则需要继续判断换下后的父节点是否符合堆的特性。
        }
    }

    /**
     * 测试用例
     *
     * 输出:
     * [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9]
     */
    public static void main(String[] args) {
        int[] arr = new int[]{3,5,3,0,8,6,1,5,8,6,2,4,9,4,7,0,1,8,9,7,3,1,2,5,9,7,4,0,2,6};
        new HeapSort(arr).sort();
        System.out.println(Arrays.toString(arr));
    }

}

tags: #数据结构 #排序 #堆