排序系列【比较排序系列之】堆排序

堆排序,顾名思义是通过直接选择排序衍生而来的。直接选择排序是直接从剩余记录中线性的查找最大记录的方法,并没有巧妙的利用前一轮查找所得到的信息,而堆排序,利用堆数据结构来保存剩余记录相对大小的信息,因而是更有效的选择排序。
堆分为最大堆和最小堆,本篇我们通过最大堆来实现我们的功能。
最大堆需要满足的条件: 堆中每个父节点中的数据项都要大于或等于其子节点中的数据项。
堆排序主要有两个步骤

  1. 对所有记录建立一个最大堆。
  2. 取出堆顶的最大记录与数组末端进行交换,最大记录放在下标n-1的位置;
  3. 对剩余堆记录进行调整,再次形成一个最大堆;
  4. 再次取出对顶的最大记录与数组末端进行交换,最大记录放在下标n-2的位置;
  5. 不断重复,直到堆为空,也就是排序完成。

示例数组如下:【49,38,65,97,76,13,27,49】
通过筛选法建最大堆的前提条件

  1. 堆的初始位置从0开始,依次递增;
  2. 若父结点的位置为i;则左孩子结点位置为2i+1;右孩子结点位置为2i+2;
  3. 筛选位置从最后一个非结点编号开始,也就是n/2-1向下取整。

    初始堆如下:
    初始堆

筛选位置从最后一个非结点编号开始,n=8,所以初始筛选位置为i=3,也就是i=97;
因为97>49,所以位置不变;然后继续比较i,i–;
i=2时,因为13<27,且65>27,所以位置依旧不变。
i=1时,因为97>76,所以比较38和97,因为38<97所以交换位置;又因为38<49所以继续交换位置,最后堆位置如下:
i=1时
i=0时,因为97>65,所以比较49和97,因为49<97,交换位置;又因为49<76,继续交换位置,最后堆位置如下:
i=0时
到此位置,排序完成,堆变成了一个最大堆。
接下来则进行交换流程,将n-1位置的值与堆顶位置的值进行交换;

1、 i=7;交换位置7上的值和堆顶的值
交换1
交换完毕,再次调整除了i=7之外的堆元素,再次转换成一个最大堆。
交换2

2、当i=6;交换位置6和堆顶的值,然后调整属于的元素;
3、当i=5;交换位置5和堆顶的值,然后调整属于的元素;

当i=1时;交换位置1和堆顶的值,交换流程到此结束,最后的堆如下:
end

以上是针对堆排序的分析流程,如下则是代码:

public static void main(String[] args) {
        int[] array = {49,38,65,97,76,13,27,49};
        heapSort(array,array.length);
    }

    /**
     * 堆排序的主过程
     * @param array
     * @param n
     */
    static void heapSort(int[] array, int n) {

        int i;
        int temp;
        for (i = n / 2 - 1; i >= 0; --i) {
            sift(array, i, n - 1);
        }
        for (i = n - 1; i > 0; --i) {
            temp = array[0];
            array[0] = array[i];
            array[i] = temp;
            sift(array, 0, i - 1);
        }

    }

    /**
     * 调整函数
     * @param arr
     * @param low
     * @param high
     */
    static void sift(int arr[], int low, int high) {
        int i = low;
        int j = 2 *i  + 1;
        int temp = arr[i];
        while (j <= high) {
            //判定左孩子结点和右孩子结点的大小,进而决定跟结点到底与谁作比较
            if (j < high && arr[j] < arr[j + 1]) {
                ++j;
            }
            if (temp < arr[j]) {
                arr[i] = arr[j];
                i = j;
                j = 2 * i + 1;
            } else {
                break;
            }
        }
        arr[i] = temp;
    }

而时间复杂度,我们通过以上分析和代码都可以发现,其堆排序的过程其实就是建最大堆和交换最大值之后重建堆的过程。
对于有N个结点的堆 ,其层数为logN.设置i表示二叉树的层编号,则第i层的结点数最多有2^i个。
就拿n=8举例:i=0,1,2,3
当i=3时,该层含有的最多结点个数为2^3=8个,因为是最底层,所以不需要比较,比较个数为logN-i=0;
当i=2时,该层含有的最多结点个数为2^2=4个,需要和其孩子结点(比较到最后一层)进行比较,logN-2=1;
当i=1时,该层含有的最多结点个数为2^1=2个,需要和其孩子结点进行比较(比较到最后一层)进行比较,logN-1=2次,所以当n=8时,logN=3,需要的比较次数为:2^3(3-3)+2^2(3-2)+2^1(3-1);
所以当结点数为N时,堆排序需要的比较次数为:∑求和,logN,i=0开始,2^i(logN-i).

而对于重建堆的过程,待定。

posted on 2018-07-27 16:34  huohuoL  阅读(517)  评论(0编辑  收藏  举报

导航