背景
最近碰到几次使用PriorityQueue的案例,想着还是不太了解这个数据结构,在此记录一下。
一、PriorityQueue是什么?
在Java中,PriorityQueue是一个实现了优先队列的类。它根据元素的自然顺序或通过比较器(Comparator)定义的顺序来维护元素的优先级。Java的PriorityQueue内部使用堆(heap)数据结构进行实现,以确保在插入和删除元素时能够维护正确的优先级顺序。它提供了高效的插入、删除和获取最高优先级元素的操作,使其成为处理优先级任务的常用选择。
堆
在Java中,大小堆(Min Heap和Max Heap)是优先队列(PriorityQueue)的内部实现之一。它是基于完全二叉树的数据结构,其中每个节点的值都小于或等于(小根堆/Min Heap)或大于或等于(大根堆/Max Heap)其子节点的值。当一棵二叉树的每个结点都大于等于它的两个子结点时,它被称为堆有序。
相应地,在堆有序的二叉树中,每个结点都小于等于它的父结点(如果有的话)。从任意结点向上,
我们都能得到一列非递减的元素;从任意结点向下,我们都能得到一列非递增的元素。
对于最大堆(max heap),父节点的值始终大于或等于其子节点的值。
对于最小堆(min heap),父节点的值始终小于或等于其子节点的值。
二叉堆
二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级储存(不使
用数组的第一个位置)。
在一个二叉堆中,位置 k 的结点的父结点的位置为k/2,而它的两个子结点的位置则分别为 2k 和 2k+1。这样在不使用指针的情况下我们也可以通过计算数组的索引在树中上下移动:从 a[k] 向上一层
就令 k 等于 k/2,向下一层则令 k 等于 2k 或 2k+1。
完全二叉树
完全二叉树是一种特殊的二叉树,除了最后一层外,每一层的节点都必须填满,并且从左到右连续排列。最后一层的节点如果存在,也必须尽量靠左排列。
具体来说,完全二叉树满足以下条件:
- 对于任意节点 i,其左子节点在位置 2i 上。
- 对于任意节点 i,其右子节点在位置 2i+1 上。
- 对于除最后一层以外的所有层次,节点数目都是满的。
这个定义确保了完全二叉树的结构紧凑且高效,因为它的特点使得使用数组等连续存储结构来表示二叉树成为可能。同时,它也可以通过对节点编号的方式进行高效的索引和遍历操作。
需要注意的是,完全二叉树并不要求节点的值有任何特定的顺序或大小关系,只关注二叉树的结构。
二、如何使用PriorityQueue
以下是一些关于Java中PriorityQueue的重要特点和用法:
1、插入和删除操作: PriorityQueue提供offer()方法用于插入元素,并根据元素的优先级进行排序。它还提供了poll()方法用于删除并返回队列中具有最高优先级的元素。
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(5);
pq.offer(3);
pq.offer(8);
int highestPriority = pq.poll(); // 返回最高优先级的元素(3)
2、自然排序和自定义比较器: 默认情况下,PriorityQueue使用元素的自然顺序来确定优先级。例如,如果元素是整数,则按升序排列。如果要使用自定义顺序,可以提供一个实现了Comparator接口的比较器对象。
PriorityQueue<String> pq = new PriorityQueue<>(Comparator.reverseOrder());
pq.offer("apple");
pq.offer("banana");
pq.offer("cherry");
String highestPriority = pq.poll(); // 返回最高优先级的元素(cherry)
3、查看优先级最高的元素: 使用peek()方法可以查看但不删除队列中的优先级最高的元素。
PriorityQueue<Double> pq = new PriorityQueue<>();
pq.offer(3.14);
pq.offer(1.23);
pq.offer(2.71);
double highestPriority = pq.peek(); // 返回最高优先级的元素(1.23)
4、大小和清空操作: size()方法返回队列中的元素数量,isEmpty()方法检查队列是否为空。要清空队列中的所有元素,可以使用clear()方法。
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(5);
pq.offer(3);
boolean isEmpty = pq.isEmpty(); // 返回 false
int queueSize = pq.size(); // 返回 2
pq.clear(); // 清空队列
三、扩展——堆排序
public static void heapSort(int[] arr) {
int n = arr.length;
// 构建最大堆(升序排序)
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// 从最后一个元素开始逐个将当前最大值交换到数组末尾
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 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) {
int swap = arr[i];
arr[i] = arr[largest];
arr[largest] = swap;
heapify(arr, n, largest);
}
}