在数据结构中,堆(Heap)是一种特殊的树形数据结构,通常用于实现优先队列。堆分为最大堆和最小堆两种类型。最大堆的每个节点的值都大于或等于其子节点的值,而最小堆的每个节点的值都小于或等于其子节点的值。Java 提供了 PriorityQueue 类来实现堆的功能。
堆的介绍
堆(Heap)是一种满足特定条件的完全二叉树,主要分为两种:如下图所示:
- 小顶堆:每个节点的值都小于或等于其子节点的值。
- 大顶堆:每个节点的值都大于或等于其子节点的值。
- 特性:
- 最底层的节点靠左填充,其它层的节点都被填满
- 根节点称为
堆顶
,最底层最靠右的节点称为堆底
- 堆的根节点(堆顶)是堆中最大(大顶堆)或最小(小顶堆)的元素。
- 堆的存储
堆通常使用数组来存储。对于一个索引为 i 的节点:
-
其左子节点的索引为 2i + 1
-
其右子节点的索引为 2i + 2
-
其父节点的索引为 (i - 1) / 2
- 堆的操作及效率
java中提供的是优先队列(PriorityQueue)
方法名 | 描述 | 时间复杂度 |
---|---|---|
add() | 元素入堆 | O(log n) |
poll() | 堆顶元素出堆 | O(log n) |
peek() | 访问堆顶元素 | O(1) |
size() | 获取堆中元素的数量 | O(1) |
isEmpty() | 堆是否为空 | O(1) |
堆排序
堆排序(Heap Sort)是一种基于堆数据结构的比较排序算法。它的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),并且是一种原地排序算法(即不需要额外的存储空间)。堆排序的核心思想是利用堆的性质来维护一个最大堆或最小堆,然后逐步将堆顶元素(最大值或最小值)取出,放到数组的末尾,最终得到一个有序的数组。
堆排序的基本步骤
- 构建最大堆
从最后一个非叶子节点开始,向前遍历每个节点,对每个节点执行堆化操作。
最后一个非叶子节点的索引为 n/2 - 1,其中 n 是数组的长度。
- 排序
将堆顶元素(最大值)与堆的最后一个元素交换。(出堆)
将堆的大小减一(即忽略最后一个元素)。
对新的堆顶元素执行堆化操作,以维持最大堆的性质。
重复上述步骤,直到堆的大小为1。
代码实现如下:
/*** 堆排序*/
public class HeapSort {public static void heapSort(int[] arr) {int n = arr.length;// 构建大顶堆for (int i = n / 2 - 1; i >= 0; i--) {heapify(arr, n, i);}// 2. 排序for (int i = n - 1; i > 0; i--) {// 将堆顶元素(最大值)与当前堆的最后一个元素交换int temp = arr[0];arr[0] = arr[i];arr[i] = temp;// 对新的堆顶元素进行堆化操作heapify(arr, i, 0);}}//堆化操作public static void heapify(int[] arr,int n,int i){int max = i; // 初始化最大值为当前节点int left = 2 * i + 1; // 左子节点int right = 2 * i + 2; // 右子节点// 如果左子节点大于当前最大值,则更新最大值if (left < n && arr[left] > arr[max]) {max = left;}// 如果右子节点大于当前最大值,则更新最大值if (right < n && arr[right] > arr[max]) {max = right;}// 如果最大值不是当前节点,则交换并继续堆化if (max != i) {int swap = arr[i];arr[i] = arr[max];arr[max] = swap;// 递归地对子树进行堆化heapify(arr, n, max);}}public static void main(String[] args) {int[] arr = {8, 10, 7, 4, 6, 2, 5, 1};heapSort(arr);System.out.println(Arrays.toString(arr));}
}
堆排序特性
- 时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)、非自适应排序
建堆操作适用时间O(n),从堆中提取最大元素时间O(logn),共n-1轮。
- 空间复杂度为O(1)、原地排序
元素堆化和堆操作都是在原数组上进行的。
- 非稳定排序
在交换堆顶元素和堆底元素时,相等元素的相对位置可能发生了改变。
优缺点
- 优点
-
时间复杂度较低,适合大规模数据排序。
-
原地排序,不需要额外的存储空间。
- 缺点
-
不稳定排序算法(相同元素的相对顺序可能改变)。
-
对于小规模数据,性能不如插入排序等简单算法。
总结
堆排序是一种高效的排序算法,尤其适用于需要原地排序的场景。它的时间复杂度为O(nlogn),并且不需要额外的存储空间。堆排序的核心在于堆化操作,通过维护堆的性质来实现排序。