堆结构在Java中通过数组模拟树形结构,核心是维护堆属性的上浮和下沉操作,堆排序利用大顶堆进行原地排序,时间复杂度稳定为O(n log n),适用于优先级队列和Top K问题。

在Java中实现堆结构和堆排序,核心在于利用数组模拟树形结构,并精心维护其“堆属性”(父节点总是大于或小于其子节点)。堆排序则是在此基础上,通过反复取出最大/最小元素来达到排序目的。说实话,这玩意儿初看有点绕,但一旦你理解了数组索引和树节点的关系,就豁然开朗了。
要实现一个堆结构,我们通常会选择用数组来承载。一个Max-Heap(大顶堆)的核心是每个父节点的值都大于或等于其子节点。堆排序,顾名思义,就是利用这种堆结构来完成数组的排序。
1. 堆结构(Max-Heap)的实现
我们可以定义一个
MaxHeap
ArrayList
ArrayList
立即学习“Java免费学习笔记(深入)”;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
public class MaxHeap {
private List<Integer> heap;
public MaxHeap() {
this.heap = new ArrayList<>();
}
// 插入元素
public void insert(int value) {
heap.add(value);
heapifyUp(heap.size() - 1); // 新元素可能破坏堆属性,需要上浮
}
// 提取最大元素(堆顶)
public int extractMax() {
if (isEmpty()) {
throw new NoSuchElementException("Heap is empty.");
}
int max = heap.get(0);
int lastElement = heap.remove(heap.size() - 1); // 移除最后一个元素
if (!isEmpty()) {
heap.set(0, lastElement); // 将最后一个元素放到堆顶
heapifyDown(0); // 新堆顶可能破坏堆属性,需要下沉
}
return max;
}
// 查看最大元素(不移除)
public int peekMax() {
if (isEmpty()) {
throw new NoSuchElementException("Heap is empty.");
}
return heap.get(0);
}
public boolean isEmpty() {
return heap.isEmpty();
}
public int size() {
return heap.size();
}
// 元素上浮操作:当新元素插入或元素值增大时,将其向上移动以维护堆属性
private void heapifyUp(int index) {
int parentIndex = (index - 1) / 2; // 计算父节点索引
while (index > 0 && heap.get(index) > heap.get(parentIndex)) {
swap(index, parentIndex);
index = parentIndex;
parentIndex = (index - 1) / 2;
}
}
// 元素下沉操作:当堆顶元素被移除或元素值减小时,将其向下移动以维护堆属性
private void heapifyDown(int index) {
int leftChildIndex = 2 * index + 1;
int rightChildIndex = 2 * index + 2;
int largestIndex = index; // 假设当前节点最大
// 检查左子节点
if (leftChildIndex < heap.size() && heap.get(leftChildIndex) > heap.get(largestIndex)) {
largestIndex = leftChildIndex;
}
// 检查右子节点
if (rightChildIndex < heap.size() && heap.get(rightChildIndex) > heap.get(largestIndex)) {
largestIndex = rightChildIndex;
}
// 如果最大值不是当前节点,则交换并继续下沉
if (largestIndex != index) {
swap(index, largestIndex);
heapifyDown(largestIndex);
}
}
// 交换两个位置的元素
private void swap(int i, int j) {
int temp = heap.get(i);
heap.set(i, heap.get(j));
heap.set(j, temp);
}
// 用于调试,打印堆内容
public void printHeap() {
System.out.println(heap.toString());
}
}2. 堆排序功能的实现
堆排序通常是原地排序,直接在原数组上操作。它分为两个主要阶段:
public class HeapSort {
// 堆排序主方法
public static void sort(int[] arr) {
int n = arr.length;
// 1. 构建大顶堆(从最后一个非叶子节点开始向上heapify)
// 最后一个非叶子节点的索引是 (n/2 - 1)
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// 2. 逐个提取元素进行排序
for (int i = n - 1; i > 0; i--) {
// 将当前最大元素(堆顶)与当前堆的最后一个元素交换
swap(arr, 0, i);
// 对剩余的元素(不包括已排序的元素)重新进行堆化
heapify(arr, i, 0); // 注意这里的n变成了i,表示堆的有效大小
}
}
// 维护堆属性的下沉操作(与MaxHeap中的heapifyDown类似,但操作的是数组)
private static void heapify(int[] arr, int n, int i) {
int largest = i; // 初始化最大元素为根节点
int left = 2 * i + 1; // 左子节点
int right = 2 * i + 2; // 右子节点
// 如果左子节点存在且大于当前最大元素
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
// 如果右子节点存在且大于当前最大元素
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
// 如果最大元素不是根节点,则交换并递归下沉
if (largest != i) {
swap(arr, i, largest);
heapify(arr, n, largest);
}
}
// 交换数组中两个元素的位置
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
// 测试MaxHeap
System.out.println("--- MaxHeap 示例 ---");
MaxHeap maxHeap = new MaxHeap();
maxHeap.insert(3);
maxHeap.insert(2);
maxHeap.insert(15);
maxHeap.insert(5);
maxHeap.insert(4);
maxHeap.insert(45);
maxHeap.printHeap(); // 应该看到一个符合大顶堆特性的数组表示
System.out.println("提取最大值: " + maxHeap.extractMax()); // 45
maxHeap.printHeap();
System.out.println("提取最大值: " + maxHeap.extractMax()); // 15
maxHeap.printHeap();
// 测试HeapSort
System.out.println("\n--- 堆排序示例 ---");
int[] data = {12, 11, 13, 5, 6, 7};
System.out.println("原始数组: " + java.util.Arrays.toString(data));
HeapSort.sort(data);
System.out.println("排序后数组: " + java.util.Arrays.toString(data));
int[] data2 = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7};
System.out.println("原始数组2: " + java.util.Arrays.toString(data2));
HeapSort.sort(data2);
System.out.println("排序后数组2: " + java.util.Arrays.toString(data2));
}
}说实话,堆这个数据结构在计算机科学里地位挺特殊的,它既不像链表那么直观,也不像哈希表那样“快得离谱”,但它在某些场景下就是无可替代。在Java里,最常见的应用就是
java.util.PriorityQueue
优点:
缺点:
适用场景:
总的来说,堆虽然有些“怪脾气”,但它在需要高效处理“最大/最小”或“优先级”这类问题的场景中,简直就是个MVP。
堆操作的核心,毫无疑问就是那两个听起来有点玄乎的“上浮”(Swim,有时也叫
heapifyUp
heapifyDown
1. 上浮(Swim / heapifyUp
想象一下,你往一个已经整理好的大顶堆里塞了一个新元素。这个新元素,我们暂时把它放在数组的最后面。但问题来了,它可能比它的父节点还大!这就破坏了堆的“父大子小”的规矩。怎么办?
上浮操作就是来解决这个问题的。它会不断地把这个“不守规矩”的新元素和它的父节点进行比较。如果新元素比父节点大,那就交换它们的位置。然后,新元素就“上浮”了一层,它会继续和新的父节点比较,直到它找到了一个比它大的父节点(或者它自己成了堆顶),这样堆的属性就恢复了。
2. 下沉(Sink / heapifyDown
下沉操作通常发生在两种情况:
无论是哪种情况,堆顶(或某个节点)都可能不再符合堆的属性。下沉操作就是让这个“不守规矩”的元素向下移动,直到它找到一个合适的位置。它会比较自己和它的两个子节点,找出其中最大的那个(对于大顶堆)。如果它自己不是最大的,就和最大的那个子节点交换位置,然后继续向下沉,直到它比两个子节点都大(或者它已经到了叶子节点)。
这两个操作就像是堆的“自愈”机制。无论你往堆里加什么,或者从堆里拿走什么,它们都能确保堆的结构始终保持着那种有序性。理解它们是理解堆性能的关键,也是自己手写堆代码时最容易出错但也最能体现功力的地方。
手写堆结构和堆排序,虽然理论上清晰,但实际敲代码时总会遇到些“坑”。这就像你看着地图觉得路很好走,真走起来才发现有小石子绊脚。
常见的挑战:
(i - 1) / 2
2 * i + 1
2 * i + 2
extractMax
peekMax
heapifyUp
heapifyDown
index > 0
childIndex < heap.size()
IndexOutOfBoundsException
>
<
n
i
以上就是java代码如何实现堆结构及堆排序功能 java代码堆数据结构的基础编写教程的详细内容,更多请关注php中文网其它相关文章!
java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号