概念:
堆是一种基于完全二叉树的数据结构,其中每个父节点都大于等于/小于等于其子节点。可以分为最大堆(Max Heap)和最小堆(Min Heap),其中最大堆要求父节点的值大于或等于所有子节点,而最小堆要求父节点的值小于或等于所有子节点。
特点:
- 使用数组实现:通过利用数组索引表示完全二叉树中元素之间关系来节省内存空间。
- 快速查找:插入、删除和获取操作具有对数时间复杂度。
- 动态扩展:能够自动调整大小以容纳更多元素。
优点:
- 高效插入和删除操作:插入和删除具有对数时间复杂度。
- 高效获取最大/最小元素:可以快速获取具有极端值的顶部元素。
缺点:
- 不支持随机访问:堆不同于数组或链表,无法直接通过索引访问特定位置上的元素。
- 中间位置元素难以访问和更新:要查找、更新或删除非顶部位置上的元素会变得复杂且低效。
适用场景:
- 需要快速获取最大/最小元素的场景。
- 常用于优先队列、排序算法(如堆排序)等领域。
示例代码:
下面是一个以最大堆为例的java代码简单实现:
import java.util.Arrays;
import java.util.NoSuchElementException;
class Heap {
private int[] heapArray;
private int maxSize;
private int currentSize;
public Heap(int size) {// 构造函数,创建一个具有指定大小的堆对象。
this.maxSize = size;
this.currentSize = 0;
this.heapArray = new int[size];
}
public boolean isEmpty() {// 检查堆是否为空,并返回布尔值。
return currentSize == 0;
}
public boolean isFull() {// 检查堆是否已满,并返回布尔值。
return currentSize == maxSize;
}
public void insert(int value) {// 向堆中插入一个元素。如果堆已满,则抛出异常。该方法使用“上溯”操作来维护堆性质。
if (isFull()) {
throw new IllegalStateException("Heap is full");
}
heapArray[currentSize] = value;
trickleUp(currentSize);
currentSize++;
}
private void trickleUp(int index) {//从给定索引开始执行上溯操作以维护堆性质。它将当前节点与其父节点进行比较和交换,直到达到合适的位置或达到根节点为止。
int parentIndex = (index - 1) / 2; // 计算父节点索引
while (index > 0 && heapArray[parentIndex] < heapArray[index]) {
// 交换父节点与子节点的值
int temp = heapArray[parentIndex];
heapArray[parentIndex] = heapArray[index];
heapArray[index] = temp;
index = parentIndex;
parentIndex = (index - 1) / 2; // 更新父节点索引
}
}
public int remove() {// 移除并返回顶部(最大)元素。如果堆为空,则抛出异常。该方法使用“下溯”操作来维护堆性质。
if (isEmpty()) {
throw new IllegalStateException("Heap is empty");
}
int root = heapArray[0]; // 根节点的值为最大/最小元素
currentSize--;
heapArray[0] = heapArray[currentSize];
trickleDown(0);
return root;
}
private void trickleDown(int index) {// 从给定索引开始执行下溯操作以维护堆性质。它将当前节点与其较大子节点进行比较和交换,直到没有子节点或找到合适的位置为止。
int largerChildIndex;
while (index < currentSize / 2) { // 判断是否有子节点
int leftChildIndex = 2 * index + 1;
int rightChildIndex= leftChildIndex + 1;
if (rightChildIndex < currentSize &&
heapArray[leftChildIndex] <heapArray[rightChildIndex]) {
largerChildIndex=rightChildIndex;
} else{
largerChildIndex=leftChildIndex ;
}
if(heapArray[index]>=heapArray[largerChildIndex]){
break; // 当前位置已满足堆性质,不需要调整,退出循环
}
swap(index, largerChildIndex);
index=largerChildIndex;//更新索引以继续下一轮比较
}
}
private void swap(int index1,int index2){ //用于交换数组中两个索引位置上的元素。
int temp=heapArray[index1];
heapArray[index1]=heapArray[index2];
heapArray[index2] = temp;
}
public int[] getHeapArray() {// 返回堆中所有元素
return Arrays.copyOf(heapArray, currentSize);
}
public int peek() {// 查看但不删除顶部元素
if (currentSize == 0) {
throw new NoSuchElementException("Heap is empty.");
}
return heapArray[0];
}
}
public class Main {
public static void main(String[] args) {
//创建一个最大堆
Heap heap = new Heap(10);
// 插入元素
heap.insert(80);
heap.insert(75);
heap.insert(60);
heap.insert(68);
heap.insert(55);
// 打印堆中所有元素
System.out.println("Current elements in the heap: " + Arrays.toString(heap.getHeapArray()));
// 获取并移除顶部(最大)元素
int maxElement=heap.remove();
System.out.println("Maximum element removed from the heap: "+maxElement);
//打印删除顶部元 素后的堆
System.out.println("Current elements in the heap after removal:" + Arrays.toString(heap.getHeapArray()) );
//插入新元素
heap.insert(100);
//查看但不删除顶部元素
maxElement=heap.peek();
System.out.println("Maximum element removed from the heap: "+maxElement);
//打印堆中所有元素
System.out.println("Current elements in the heap: " + Arrays.toString(heap.getHeapArray()));
}
}
常见问题:
- 元素大小问题:在构建或插入时,需要确保父节点与子节点具有正确的大小关系。如果使用自定义对象作为堆中的元素,请实现正确的比较器来定义大小关系。
- 动态调整容量:如果底层数组满了,则需要进行动态扩展以容纳更多的元素。这可能涉及重新分配内存和复制数据,并带来一些性能开销。
- 并发问题:Java提供了线程安全的优先队列实现,如
PriorityBlockingQueue
。如果在多线程环境中使用,请确保选择适当的实现以避免并发问题。
总结:
堆是一种基于完全二叉树的数据结构,通过父节点与子节点之间的比较来维护其特性。它支持快速插入、删除和获取最大/最小元素的操作,并用于优先队列和排序算法(如堆排序)等场景。处理大小关系、动态调整容量和并发问题时要注意正确性和性能。