手撕Java系列-数据结构之最大堆
最大堆(Max Heap)是一种特殊的完全二叉树,其中每个节点的值都不小于其子节点的值。最大堆通常用于实现优先队列,因为可以在 (O(\log n)) 时间内完成插入和删除最大元素的操作。
·
最大堆(Max Heap)是一种特殊的完全二叉树,其中每个节点的值都不小于其子节点的值。最大堆通常用于实现优先队列,因为可以在 (O(\log n)) 时间内完成插入和删除最大元素的操作。
基本性质
- 完全二叉树:最大堆是一个完全二叉树,这意味着除了最后一层外,其他每一层都被完全填满,并且最后一层的所有节点都尽可能靠左。
- 堆属性:对于任意节点 (i),其父节点的值大于或等于其子节点的值。用公式表示为:
- ( \text{parent}(i) = \left\lfloor \frac{i-1}{2} \right\rfloor )
- ( \text{left}(i) = 2i + 1 )
- ( \text{right}(i) = 2i + 2 )
- ( \text{heap}[i] \geq \text{heap}[\text{left}(i)] ) 且 ( \text{heap}[i] \geq \text{heap}[\text{right}(i)] )
常见操作
- 插入元素:将新元素添加到堆的末尾,然后通过上滤(heapify up 或 bubble up)操作将其调整到正确的位置。
- 删除最大元素:移除堆顶元素(最大值),将最后一个元素移到堆顶,然后通过下滤(heapify down 或 sink)操作将其调整到正确的位置。
- 构建堆:从数组构建最大堆的过程称为堆化(heapify)。
示例代码
以下是使用 Java 实现的最大堆的示例代码:
public class MaxHeap<T extends Comparable<T>> {
private T[] heap;
private int size;
public MaxHeap(int capacity) {
this.heap = (T[]) new Comparable[capacity];
this.size = 0;
}
// 插入元素
public void insert(T value) {
if (size == heap.length) {
throw new IllegalStateException("Heap is full");
}
heap[size] = value;
siftUp(size);
size++;
}
// 上滤操作
private void siftUp(int index) {
while (index > 0 && heap[index].compareTo(heap[parent(index)]) > 0) {
swap(index, parent(index));
index = parent(index);
}
}
// 删除最大元素
public T extractMax() {
if (size == 0) {
throw new NoSuchElementException("Heap is empty");
}
T max = heap[0];
heap[0] = heap[--size];
heap[size] = null;
siftDown(0);
return max;
}
// 下滤操作
private void siftDown(int index) {
while (left(index) < size) {
int maxChildIndex = getMaxChildIndex(index);
if (heap[index].compareTo(heap[maxChildIndex]) >= 0) {
break;
}
swap(index, maxChildIndex);
index = maxChildIndex;
}
}
// 获取左右子节点中较大的子节点索引
private int getMaxChildIndex(int index) {
int leftChildIndex = left(index);
int rightChildIndex = right(index);
if (rightChildIndex < size && heap[rightChildIndex].compareTo(heap[leftChildIndex]) > 0) {
return rightChildIndex;
}
return leftChildIndex;
}
// 交换两个节点的值
private void swap(int i, int j) {
T temp = heap[i];
heap[i] = heap[j];
heap[j] = temp;
}
// 获取父节点索引
private int parent(int index) {
return (index - 1) / 2;
}
// 获取左子节点索引
private int left(int index) {
return 2 * index + 1;
}
// 获取右子节点索引
private int right(int index) {
return 2 * index + 2;
}
// 构建堆
public void buildHeap(T[] array) {
for (int i = 0; i < array.length; i++) {
heap[i] = array[i];
size++;
}
for (int i = parent(size - 1); i >= 0; i--) {
siftDown(i);
}
}
// 打印堆
public void printHeap() {
for (int i = 0; i < size; i++) {
System.out.print(heap[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
MaxHeap<Integer> maxHeap = new MaxHeap<>(10);
maxHeap.insert(3);
maxHeap.insert(1);
maxHeap.insert(4);
maxHeap.insert(1);
maxHeap.insert(5);
maxHeap.insert(9);
maxHeap.insert(2);
maxHeap.insert(6);
maxHeap.insert(5);
maxHeap.insert(3);
System.out.println("Initial Max Heap:");
maxHeap.printHeap();
System.out.println("Extracted Max: " + maxHeap.extractMax());
System.out.println("Max Heap after extraction:");
maxHeap.printHeap();
Integer[] array = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
MaxHeap<Integer> maxHeap2 = new MaxHeap<>(array.length);
maxHeap2.buildHeap(array);
System.out.println("Built Max Heap from array:");
maxHeap2.printHeap();
}
}
解释
-
插入元素:
- 将新元素添加到堆的末尾。
- 通过
siftUp操作将新元素向上调整到正确的位置,以保持堆的性质。
-
删除最大元素:
- 移除堆顶元素(最大值)。
- 将最后一个元素移到堆顶。
- 通过
siftDown操作将堆顶元素向下调整到正确的位置,以保持堆的性质。
-
构建堆:
- 从数组构建最大堆。
- 从最后一个非叶子节点开始,逐个进行
siftDown操作,以确保整个数组满足最大堆的性质。
应用场景
最大堆在以下场景中非常有用:
- 优先队列:最大堆可以高效地实现优先队列,其中插入和删除最大元素的时间复杂度为 (O(\log n))。
- 选择算法:最大堆可以用于选择算法,例如找到数组中的第 k 大元素。
- 堆排序:最大堆可以用于实现堆排序算法,时间复杂度为 (O(n \log n))。
当然,下面是最大堆在不同应用场景中的示例代码和解释。
1. 优先队列
优先队列是一种特殊的数据结构,其中每个元素都有一个优先级,优先级最高的元素最先被取出。最大堆非常适合实现优先队列。
import java.util.NoSuchElementException;
public class PriorityQueue<T extends Comparable<T>> {
private MaxHeap<T> heap;
public PriorityQueue(int capacity) {
heap = new MaxHeap<>(capacity);
}
// 插入元素
public void enqueue(T value) {
heap.insert(value);
}
// 删除并返回优先级最高的元素
public T dequeue() {
return heap.extractMax();
}
// 检查优先队列是否为空
public boolean isEmpty() {
return heap.size == 0;
}
// 获取优先队列的大小
public int size() {
return heap.size;
}
public static void main(String[] args) {
PriorityQueue<Integer> pq = new PriorityQueue<>(10);
pq.enqueue(3);
pq.enqueue(1);
pq.enqueue(4);
pq.enqueue(1);
pq.enqueue(5);
pq.enqueue(9);
pq.enqueue(2);
pq.enqueue(6);
pq.enqueue(5);
pq.enqueue(3);
System.out.println("Initial Priority Queue:");
while (!pq.isEmpty()) {
System.out.print(pq.dequeue() + " ");
}
System.out.println();
}
}
- 使用最大堆实现优先队列,通过
enqueue方法插入元素,通过dequeue方法删除并返回优先级最高的元素。 - 示例代码展示了如何使用优先队列处理一组整数,并按优先级顺序输出。
2. 选择算法
最大堆可以用于选择算法,例如找到数组中的第 k 大元素。
public class SelectionAlgorithm {
public static int findKthLargest(int[] nums, int k) {
MaxHeap<Integer> maxHeap = new MaxHeap<>(nums.length);
for (int num : nums) {
maxHeap.insert(num);
}
for (int i = 0; i < k - 1; i++) {
maxHeap.extractMax();
}
return maxHeap.extractMax();
}
public static void main(String[] args) {
int[] nums = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
int k = 3;
int kthLargest = findKthLargest(nums, k);
System.out.println("The " + k + "rd largest element is: " + kthLargest);
}
}
- 使用最大堆找到数组中的第 k 大元素。
- 先将所有元素插入最大堆,然后连续提取 k-1 次最大元素,最后提取的元素即为第 k 大元素。
- 示例代码展示了如何找到数组中的第 3 大元素。
3. 堆排序
堆排序是一种基于最大堆的排序算法,时间复杂度为 (O(n \log n))。
public class HeapSort {
public static void sort(int[] arr) {
MaxHeap<Integer> maxHeap = new MaxHeap<>(arr.length);
maxHeap.buildHeap(arr);
for (int i = 0; i < arr.length; i++) {
arr[arr.length - 1 - i] = maxHeap.extractMax();
}
}
public static void main(String[] args) {
int[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
System.out.println("Original array:");
for (int num : arr) {
System.out.print(num + " ");
}
System.out.println();
sort(arr);
System.out.println("Sorted array:");
for (int num : arr) {
System.out.print(num + " ");
}
System.out.println();
}
}
- 使用最大堆对数组进行排序。
- 先构建最大堆,然后依次提取最大元素并放入数组的末尾,直到所有元素都被提取。
- 示例代码展示了如何对一组整数进行堆排序。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)