二叉堆是一种完全二叉树数据结构,具有以下特性:
-
最小堆:每个节点的值都小于或等于其子节点的值
-
最大堆:每个节点的值都大于或等于其子节点的值
二叉堆在计算机科学中有广泛的应用,主要得益于其高效的插入和删除操作(O(log n)时间复杂度)以及快速访问极值(O(1)时间复杂度)的特性。
最小堆和最大堆是部分有序的数据结构,而不是完全有序的。它们遵循特定的堆性质,但不同于完全排序的数组或列表。
1. 堆的性质
-
最小堆:每个节点的值都小于或等于其子节点的值
-
最大堆:每个节点的值都大于或等于其子节点的值
2. 有序性表现
-
层级有序:只保证父节点与子节点之间的关系,不保证兄弟节点之间的顺序
-
局部有序:从根节点到任意叶子节点的路径是有序的(最小堆递减,最大堆递增)
-
全局无序:不同分支上的节点没有顺序关系
特性 | 最小堆/最大堆 | 完全排序数组 |
---|---|---|
根节点值 | 最小/最大元素 | 最小/最大元素 |
兄弟节点顺序 | 无保证 | 完全有序 |
插入时间复杂度 | O(log n) | O(n) |
删除极值时间 | O(log n) | O(1) |
查找任意元素 | O(n) | O(log n) |
最小堆示例:
1
/ \
3 2
/ \ /
4 5 6
-
满足父节点≤子节点
-
但兄弟节点3和2是无序的(3>2)
-
子树之间也无序(右子树2比左子树的4,5小)
最大堆示例:
6
/ \
4 5
/ \ /
3 2 1
-
满足父节点≥子节点
-
但兄弟节点4和5是无序的
-
子树之间也无序
Top K 问题是指从一个数据集中找出最大(或最小)的 K 个元素。二叉堆(特别是最小堆或最大堆)是解决这类问题的高效数据结构。
1. 使用最小堆解决 Top K 最大元素问题
步骤:
-
初始化一个大小为 K 的最小堆
-
遍历数据集中的每个元素:
-
如果堆中元素数量小于 K,直接插入堆
-
否则,比较当前元素与堆顶(最小元素):
-
如果当前元素 > 堆顶元素,替换堆顶元素并堆化
-
否则跳过
-
-
-
遍历完成后,堆中保存的就是最大的 K 个元素
python实现:
import heapq
def top_k_largest(nums, k):
min_heap = []
for num in nums:
if len(min_heap) < k:
heapq.heappush(min_heap, num)
else:
if num > min_heap[0]:
heapq.heappop(min_heap)
heapq.heappush(min_heap, num)
return min_heap
# 示例
nums = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
k = 3
print(top_k_largest(nums, k)) # 输出 [5, 6, 9]
java实现:
import java.util.PriorityQueue;
public class TopKLargest {
public static int[] topKLargest(int[] nums, int k) {
// 创建最小堆
PriorityQueue<Integer> minHeap = new PriorityQueue<>(k);
for (int num : nums) {
if (minHeap.size() < k) {
minHeap.offer(num);
} else {
if (num > minHeap.peek()) {
minHeap.poll();
minHeap.offer(num);
}
}
}
// 将堆中的元素转换为数组
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = minHeap.poll();
}
return result;
}
public static void main(String[] args) {
int[] nums = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
int k = 3;
int[] result = topKLargest(nums, k);
System.out.println("Top " + k + " largest elements:");
for (int num : result) {
System.out.print(num + " ");
}
// 输出: 5 5 6 9 (顺序可能不同,因为堆不保证顺序)
}
}
2. 使用最大堆解决 Top K 最小元素问题
步骤:
-
初始化一个大小为 K 的最大堆
-
遍历数据集中的每个元素:
-
如果堆中元素数量小于 K,直接插入堆
-
否则,比较当前元素与堆顶(最大元素):
-
如果当前元素 < 堆顶元素,替换堆顶元素并堆化
-
否则跳过
-
-
-
遍历完成后,堆中保存的就是最小的 K 个元素
python实现:
import heapq
def top_k_smallest(nums, k):
# Python的heapq模块只实现最小堆,所以用负数模拟最大堆
max_heap = []
for num in nums:
if len(max_heap) < k:
heapq.heappush(max_heap, -num)
else:
if num < -max_heap[0]:
heapq.heappop(max_heap)
heapq.heappush(max_heap, -num)
return [-x for x in max_heap]
# 示例
nums = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
k = 3
print(top_k_smallest(nums, k)) # 输出 [1, 1, 2]
java实现:
import java.util.PriorityQueue;
import java.util.Collections;
public class TopKSmallest {
public static int[] topKSmallest(int[] nums, int k) {
// 创建最大堆(使用Collections.reverseOrder())
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, Collections.reverseOrder());
for (int num : nums) {
if (maxHeap.size() < k) {
maxHeap.offer(num);
} else {
if (num < maxHeap.peek()) {
maxHeap.poll();
maxHeap.offer(num);
}
}
}
// 将堆中的元素转换为数组
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = maxHeap.poll();
}
return result;
}
public static void main(String[] args) {
int[] nums = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
int k = 3;
int[] result = topKSmallest(nums, k);
System.out.println("Top " + k + " smallest elements:");
for (int num : result) {
System.out.print(num + " ");
}
// 输出: 1 1 2 (顺序可能不同)
}
}