堆排序
1. 什么是堆排序?
堆排序是一种基于堆这种数据结构的排序算法。堆是一种特殊的完全二叉树,它满足以下性质:
- 大顶堆:每个非叶子节点的值都大于或等于其左右子节点的值,根节点的值是最大的。
- 小顶堆:每个非叶子节点的值都小于或等于其左右子节点的值,根节点的值是最小的。
堆排序的思想是利用大顶堆或小顶堆来选出最大或最小的元素,然后将其放到数组的末尾,再对剩余的元素重复这个过程,直到数组有序。
2. 堆排序的步骤
堆排序的步骤如下:
- 构建初始堆,将待排序数组调整为一个大顶堆(升序)或小顶堆(降序)。
- 将堆顶元素与数组末尾元素交换,并从数组中移除。
- 对剩余的数组重新调整为堆。
- 重复步骤2和3,直到数组只剩一个元素。
3. 堆排序的示例
假设我们要对以下数组进行升序排序:
[16, 7, 3, 20, 17, 8]
我们可以按照以下步骤进行:
- 构建初始大顶堆,
-
- 从最后一个非叶子节点开始,自下而上,自右而左调整每个节点,使其满足大顶堆性质。
- 最后一个非叶子节点是16,它已经满足大顶堆性质,所以不需要调整。
- 倒数第二个非叶子节点是7,它的右子节点20比它大,所以交换它们的位置。
- 然后继续调整20所在的子树,发现它的左子节点17比它大,所以再次交换它们的位置。
- 此时得到一个大顶堆:
[20, 17, 16, 7, 3, 8]
- 将堆顶元素20与数组末尾元素8交换,并从数组中移除20。此时数组变为:
[8, 17, 16, 7, 3] | [20]
- 对剩余的数组重新调整为大顶堆。从根节点开始,比较它与左右子节点的大小,如果有比它大的子节点,就交换它们的位置,并继续调整交换后的子树。此时得到一个新的大顶堆:
[17, 8, 16, 7, 3] | [20]
- 将堆顶元素17与数组末尾元素3交换,并从数组中移除17。此时数组变为:
[3, 8, 16, 7] | [17, 20]
- 对剩余的数组重新调整为大顶堆。得到:
[16, 8, 3, 7] | [17, 20]
- 将堆顶元素16与数组末尾元素7交换,并从数组中移除16。此时数组变为:
[7, 8, 3] | [16, 17, 20]
- 对剩余的数组重新调整为大顶堆。得到:
[8, 7, 3] | [16, 17, 20]
- 将堆顶元素8与数组末尾元素3交换,并从数组中移除8。此时数组变为:
[3, 7] | [8, 16, 17, 20]
- 对剩余的数组重新调整为大顶堆。得到:
[7, 3] | [8, 16, 17, 20]
- 将堆顶元素7与数组末尾元素3交换,并从数组中移除7。此时数组变为:
3 | [7, 8, 16, 17, 20]
- 对剩余的数组重新调整为大顶堆。得到:
3 | [7, 8, 16, 17, 20]
- 将堆顶元素3与数组末尾元素3交换,并从数组中移除3。此时数组变为:
[] | [3, 7, 8, 16, 17, 20]
排序完成。
4. 堆排序的Java实现
public class HeapSort {
public static void main(String[] args) {
int[] arr = {16, 7, 3, 20, 17, 8};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void heapSort(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--) {
swap(arr, 0, j);
adjustHeap(arr, 0, j);
}
}
public static void adjustHeap(int[] arr, int i, int len) {
//保存当前节点的值
int temp = arr[i];
//找到左子节点的位置
int k = i * 2 + 1;
//遍历所有子节点
while (k < len) {
//如果有右子节点,且右子节点大于左子节点,选择右子节点
if (k + 1 < len && arr[k] < arr[k + 1]) {
k++;
}
//如果子节点大于父节点,将子节点赋值给父节点
if (arr[k] > temp) {
arr[i] = arr[k];
//继续比较下一层子节点
i = k;
k = i * 2 + 1;
} else {
//如果父节点大于子节点,退出循环
break;
}
}
//将原来的父节点值赋值给最终的子节点
arr[i] = temp;
}
public static void swap(int[] arr, int i, int j) {
//交换两个元素的位置
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
5. 堆排序的性能分析
堆排序的时间复杂度是O(nlogn),因为每次调整堆的时间复杂度是O(logn),而调整堆的次数是O(n)。
堆排序的空间复杂度是O(1),因为只需要一个额外的变量来交换元素。
堆排序是一种不稳定的排序算法,因为在交换堆顶和末尾元素时,可能会破坏相同元素的相对位置。
6. 参考资料
- https://www.cnblogs.com/luomeng/p/10618709.html
- https://blog.csdn.net/d875708765/article/details/108531476
- https://blog.csdn.net/allway2/article/details/114093981