堆排序(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]的最大堆结构如图所示。
数组状态如下。
2.获取最大元素,并与最后一个没有替换过的元素进行交换。
3.对堆进行维护,保持堆的性质。具体维护的细节在之前的博客中有提到。
4.重复2-3过程,直到数组排序完成。
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
站在前人的肩膀上前行,感谢以下博客及文献的支持。