[数据结构与算法]-排序算法之堆排序(HeapSort)及其实现(Java)

一.什么是堆排序?

堆排序(HeapSort)顾名思义,一想就是跟堆有关,也就是说利用堆进行排序。如果对堆不是很了解的朋友,可以参考我之前写过的一篇介绍堆的博客 [数据结构与算法]-二叉堆(binary heap)介绍及其实现(Java)。

堆排序的思路如下:

对给定元素个数为N的数组arr,我们先把arr构建成最大堆(max-heap)结构。然后获取堆的最大值,即第一个元素,与最后一个没有替换过的元素进行交换。这个过程重复N - 1次。最后得到的数组,就是有序的数组了。

二.堆排序的举例实践

第一节的话总是那么让人难以理解。所以按照惯例,我们用栗子 + 图的方式,来解析堆排序的每一步过程。

下面我们对数组[78, 23, 43, 22, 0, 98, 38]进行排序

1.首先,我们先将数组构建成堆。构建过程在这里就不详解了,在前面提到的有关堆的博客中有相关的详细内容。数组[78, 23, 43, 22, 0, 98, 38]的最大堆结构如图所示。

[数据结构与算法]-排序算法之堆排序(HeapSort)及其实现(Java)_第1张图片

数组状态如下。

[数据结构与算法]-排序算法之堆排序(HeapSort)及其实现(Java)_第2张图片

2.获取最大元素,并与最后一个没有替换过的元素进行交换。

[数据结构与算法]-排序算法之堆排序(HeapSort)及其实现(Java)_第3张图片

3.对堆进行维护,保持堆的性质。具体维护的细节在之前的博客中有提到。

[数据结构与算法]-排序算法之堆排序(HeapSort)及其实现(Java)_第4张图片

4.重复2-3过程,直到数组排序完成。

[数据结构与算法]-排序算法之堆排序(HeapSort)及其实现(Java)_第5张图片

[数据结构与算法]-排序算法之堆排序(HeapSort)及其实现(Java)_第6张图片

5.总结一下,堆排序的过程:
a. 将原数组构建成一个堆,根据升序降序需求选择最大堆或最小堆;
b. 将堆顶元素与末尾元素交换,将最大或最小元素"沉"到数组末端;
c. 维护堆结构
d. 重复bc两个步骤,直到整个序列有序。

三.代码实现

HeapSort.java

public class HeapSort {
    public static > void heapSort(T[] array) {
        // 创建堆
        for (int i = array.length / 2 - 1; i >= 0; i--) {
            // 下滤
            percolateDown(array, i, array.length);
        }

        // 排序
        for (int i = array.length - 1; i > 0; i--) {
            // 将最大元素(第0位元素)与第i位元素置换
            swapReferences(array, 0, i);
            // 下滤
            percolateDown(array, 0, i);
        }
    }

    /**
     * 获取索引为i的节点的左节点索引
     *
     * @param i i节点的索引
     * @return 左节点的索引
     */
    private static int getLeftChildIndex(int i) {
        return i * 2 + 1;
    }

    /**
     * 下滤
     *
     * @param array 原数组
     * @param i     当前节点索引
     * @param n     元素个数
     * @param 
     */
    private static > void percolateDown(T[] array, int i, int n) {
        int child;
        T tmp = array[i];
        for (; getLeftChildIndex(i) < n; i = child) {
            child = getLeftChildIndex(i);
            if (child != n - 1 && array[child].compareTo(array[child + 1]) < 0) {
                child++;
            }
            if (tmp.compareTo(array[child]) < 0) {
                array[i] = array[child];
            } else {
                break;
            }
        }
        array[i] = tmp;
    }

    /**
     * 调换索引1和索引2两处的元素
     *
     * @param array  原数组
     * @param index1 索引1
     * @param index2 索引2
     * @param 
     */
    public static  void swapReferences(T[] array, int index1, int index2) {
        T tmp = array[index1];
        array[index1] = array[index2];
        array[index2] = tmp;
    }
}

HeapSortTest.java测试类

public class HeapSortTest {
    public static void main(String[] args) {
        Integer[] arr = new Integer[]{78, 23, 43, 22, 0, 98, 38};
        HeapSort.heapSort(arr);
        print(arr);
    }

    private static void print(Integer[] arr) {
        System.out.print("堆排序后的结果为: ");
        for (Integer item : arr) {
            System.out.print(item + " ");
        }
    }
}

输出为:

堆排序后的结果为: 0 22 23 38 43 78 98 

四.有关堆排序的复杂度分析

首先堆排序是个不稳定排序。

其次,对于其时间复杂度。构建堆的复杂度为O(N),在交换并重建堆的过程中,需交换N - 1次。根据完全二叉树的性质,[log2(N-1),log2(N-2)…1]逐步递减,近似为NlogN。所以堆排序时间复杂度为O(NlogN)。


有关[数据结构与算法]的学习内容已经上传到github,喜欢的朋友可以支持一下。 data-structures-and-algorithm-study-notes-java


站在前人的肩膀上前行,感谢以下博客及文献的支持。

  • 《数据结构与算法分析(第3 版) 工业出版社》
  • 图解排序算法(三)之堆排序

你可能感兴趣的:(数据结构与算法,一起学习数据结构与算法)