http://bubkoo.com/2014/01/14/sort-algorithm/heap-sort/

1 什么是堆?

堆(二叉堆)可以视为一棵完全的二叉树,完全二叉树的一个“优秀”的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示(普通的一般的二叉树通常用链表作为基本容器表示),每一个结点对应数组中的一个元素。

堆_第1张图片
堆与数组关系,当下标从1开始

对于给定的某个结点的下标 i,可以很容易的计算出这个结点的父结点、孩子结点的下标:

  • Parent(i) = i/2,i 的父节点下标
  • Left(i) = 2i,i 的左子节点下标
  • Right(i) = 2i + 1,i 的右子节点下标
堆_第2张图片
当下标从0开始

当下标从0开始,几个计算公式也要作出相应调整:

  • Parent(i) = (i-1)/2,i 的父节点下标
  • Left(i) = 2i + 1,i 的左子节点下标
  • Right(i) = 2(i + 1),i 的右子节点下标
2 最大堆与最小堆
  • 最大堆中的最大元素值出现在根结点(堆顶)
  • 堆中每个父节点的元素值都大于等于其孩子结点(如果存在)
堆_第3张图片
最大堆
  • 最小堆中的最小元素值出现在根结点(堆顶)
  • 堆中每个父节点的元素值都小于等于其孩子结点(如果存在)
堆_第4张图片
最小堆
3 堆排序原理

1 插入
每次插入都是将新数据放在数组最后,接着从下至上进行交换。
i为插入的结点索引,也是堆数组插入后的有效长度-1

void MinHeapFixup(int a[], int i)  
{  
    for (int j = (i - 1) / 2; (j >= 0 && i != 0)&& a[i] > a[j]; i = j, j = (i - 1) / 2)  
        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
}  

2 删除
或称重排。按定义,堆中每次都只能删除第0个数据。
为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,
然后再从根结点开始进行一次从上向下的调整。
i为需要调整的结点,n为结点总数
正常删除时,首先把最后的元素覆盖掉第0个元素,数组长度n-1,i=0

 void HeapDelete(int a[], int i, int n){
        while(i < n){
            int max = i;
            int left = 2 * max + 1;
            int right = 2 * (max + 1);
            if(a[max] < a[left]){
                max = left;
            }
            if(a[max] < a[right]){
                max = right;
            }
            if(max != i){
                int tmp = a[i];
                a[i] = a[max];
                a[max] = tmp;
                i = max;
            }else {
                break;
            }
        }
    }

3 创建堆
创建堆是将一个数组想象成完全二叉树,从倒数第一个父节点开始做堆重排(即堆删除后的重排操作),然后当前结点不断往上移动直到根节点

void HeapBuild(int[] a, int n) {
        int i;
        int parent = ((n - 1) / 2);
        for (i = parent; i >= 0; i--) {
            HeapDelete(a, i, n);
        }
 }

4 堆排序
使用最小堆排序后是递减数组,要得到递增数组,可以使用最大堆。
第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。有点类似于直接选择排序。

void HeapSort(int[] a, int n) {
        for (int i = n - 1; i >= -1; i++){
            int tmp = a[i];
            a[i] = a[0];
            a[0] = tmp;
            HeapDelete(a,0,i);
        }
 }

由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。二次操作时间相加还是O(N * logN)。故堆排序的时间复杂度为O(N * logN)。

你可能感兴趣的:(堆)