堆排序算法

1.介绍:

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种,它的最坏,最好,平均时间复杂度均为O(nlogn)。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。

2.什么是堆:

堆具有以下性质:

  • 它是完全二叉树:
  • 每个结点的值都大于或等于(小于或者等于)其左右孩子结点的值。

对堆中的节点进行从上到下,从左到右进行编号,对应着数组中的每个元素的下标。以大顶堆为例,堆中的逻辑结构对应着数组中的存储结构如下所示:

所以对于堆中的父子节点之间对应关系为:

大顶堆:arr[ i ] >= arr[ 2 * i + 1] 并且 arr[ i ] >= arr[ 2 * i + 2] ,其中  0 <= i <= arr.length - 1

小顶堆:arr[ i ] <= arr[ 2 * i + 1] 并且 arr[ i ] <= arr[ 2 * i + 2] ,其中  0 <= i <= arr.length - 1

其中,最后一个非叶子节点的编号(下标)为:arr.length / 2 - 1

 

3.堆排序基本思想:

这里我们以大顶堆来举例,小顶堆的思想类似。一共分为3步:

1. 将待排序数组创建(调整)成一个大顶堆,此时,堆顶元素是数组的最大值。

2. 将堆顶元素与末尾元素进行交换,此时末尾元素为最大值。

3. 将剩余的元素[0.....n-1]重新调整成一个大顶堆,现在问题就转换为了对这n-1个元素进行堆排序了。反复执行以上步骤,直到全部元素都有序。

下面我们以大顶堆为例,来演示堆排序的过程:


创建堆:

我们的目的是使得堆顶为最大的元素。首先,叶子节点满足堆的性质,我们不需要调整。从最后一个非叶子节点开始,它的下标为:arr.length / 2 - 1,即上图中的下标为2,元素值为1的节点,我们令当前调整的元素为节点i,让当前的节点与左右孩子节点进行比较,取左右孩子节点的最大值。(1)如果孩子节点的最大值不比节点i大,那么就不交换,继续调整节点i-1;(2)如果孩子节点的最大值比当前节点要大,那么就交换。交换以后,被交换的孩子节点可能不满足堆的性质,所以我们需要继续对孩子节点进行同样的操作,直到节点i对应的分支全部满足堆结构。节点i开始,每次i = i - 1 ,一直到堆顶节点。这样当所有的非叶子节点都调整为满足堆结构以后,堆顶元素就是最大的。

步骤1:

步骤2:


步骤3:


步骤4:

由于步骤3中12和8的交换导致孩子节点(节点值为8)分支不满足堆结构,所以需要调整孩子节点。


至此:大顶堆就创建好了,堆顶元素为最大的,接下来把堆顶元素和末尾元素进行交换。

首尾交换:


此时尾部元素12已经有序,并且为最大的,可以把它砍掉,我们继续对剩下的n-1个元素进行同样的操作就可以了。把1放在堆顶后,目前的结构不满足堆的性质,需要对堆顶元素按照上面的步骤进行调整,直到数组中的所有元素有序为止。

 

4.代码演示(Java版):

import java.util.Arrays;
public class MaxHeap {
    public static void main(String[] args) {
        int[] arr = new int[]{8,6,1,12,10,7};
        sortHeap(arr);
        System.out.println(Arrays.toString(arr));
    }
    
    /**
     * 堆排序
     * @param arr
     */
    public static void sortHeap(int[] arr){
        //构建最大堆
        for(int i = arr.length / 2 - 1; i >= 0; i--){
            adjustHeap(arr, i, arr.length);
        }
        //首位交换,并重新调整堆结构
        for(int j = arr.length - 1; j >= 0; j--){
            //交换
            int temp = arr[j];
            arr[j] = arr[0];
            arr[0] = temp;
            //继续调整
            adjustHeap(arr, 0, j);
        }
    }
    
    /**
     * 调整最大堆
     * @param arr 原数组
     * @param i   当前需要调整的节点的编号 
     * @param length 剩余节点的个数
     */
    public static void adjustHeap(int[] arr,int i,int length){
        int temp = arr[i];//当前节点的值
        for(int k = 2 * i + 1; k < length; k = 2 * k + 1){
            //获取当前节点的孩子节点的最大值
            if(k + 1 < length && arr[k + 1] > arr[k]){
                k = k + 1;
            }
            //如果孩子节点更大,则把最大的孩子节点的值赋值给父节点
            if(arr[k] > temp){
                arr[i] = arr[k];
                i = k;
            }
            else break; //说明当前节点往下都不需要调整了
        }
        arr[i] = temp;
    }
}

 

posted @ 2018-04-30 12:06  neu_张康  阅读(1205)  评论(0编辑  收藏  举报