Java集合-PriorityQueue优先队列

在 Java 中,PriorityQueue(优先级队列)是一种基于优先级堆的队列实现,它能够保证每次取出的元素都是队列中优先级最高的元素(默认是自然排序的最小元素)。与普通队列的 "先进先出"(FIFO)不同,PriorityQueue的元素顺序由其优先级决定。

基本特性

  1. 基于堆结构:内部使用数组实现的二叉小顶堆(默认)或大顶堆
  2. 排序方式
    • 自然排序(元素需实现Comparable接口)
    • 自定义排序(通过Comparator接口)
  3. 非线程安全:多线程环境下需使用PriorityBlockingQueue
  4. 容量动态增长:默认初始容量为 11,可自动扩容
  5. 允许 null 元素:但不推荐,会导致NullPointerException

常用方法

  • add(E e)/offer(E e):添加元素(超出容量时,add 抛异常,offer 返回 false)
  • poll():移除并返回优先级最高的元素(队列为空时返回 null)
  • peek():返回但不移除优先级最高的元素(队列为空时返回 null)
  • size():返回元素数量
  • isEmpty():判断是否为空
  • clear():清空队列

使用实例

自然排序【默认小根堆】
import java.util.PriorityQueue;

public class PriorityQueueExample {
    public static void main(String[] args) {
        // 创建默认的优先级队列(小顶堆)
        PriorityQueue pq = new PriorityQueue<>();
        
        // 添加元素
        pq.add(3);
        pq.add(1);
        pq.add(2);
        
        // 输出元素(每次取出最小元素)
        while (!pq.isEmpty()) {
            System.out.println(pq.poll()); // 输出:1, 2, 3
        }
    }
}
自定义排序【大根堆】
import java.util.Comparator;
import java.util.PriorityQueue;

public class MaxHeapExample {
    public static void main(String[] args) {
        // 创建大顶堆(通过自定义比较器)
        PriorityQueue maxHeap = new PriorityQueue<>(
            new Comparator() {
                @Override
                public int compare(Integer a, Integer b) {
                    return b - a; // 降序排列
                }
            }
        );
        
        maxHeap.add(3);
        maxHeap.add(1);
        maxHeap.add(2);
        
        while (!maxHeap.isEmpty()) {
            System.out.println(maxHeap.poll()); // 输出:3, 2, 1
        }
    }
}

自定义对象

import java.util.PriorityQueue;

// 自定义对象需实现Comparable接口
class Student implements Comparable {
    private String name;
    private int score;
    
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    
    // 按分数升序排列
    @Override
    public int compareTo(Student other) {
        return this.score - other.score;
    }
    
    @Override
    public String toString() {
        return name + "(" + score + ")";
    }
}

public class CustomObjectExample {
    public static void main(String[] args) {
        PriorityQueue students = new PriorityQueue<>();
        
        students.add(new Student("Alice", 85));
        students.add(new Student("Bob", 92));
        students.add(new Student("Charlie", 78));
        
        while (!students.isEmpty()) {
            System.out.println(students.poll()); 
            // 输出:Charlie(78), Alice(85), Bob(92)
        }
    }
}

注意事项

  1. PriorityQueueiterator()方法不能保证按优先级顺序遍历元素
  2. 如果需要按顺序遍历,需先将元素全部取出(如使用poll()方法)
  3. 对于自定义对象,必须实现Comparable接口或提供Comparator,否则会抛出ClassCastException
  4. 时间复杂度:
    • 插入元素:O (log n)
    • 获取 / 删除队首元素:O (log n)
    • 查找操作:O (n)

为什么iterator()方法不能保证按优先级顺序遍历元素?

1. 内部结构是堆,而非有序数组 / 链表

PriorityQueue底层使用二叉堆(默认是小顶堆)实现,堆的结构特点是:

  • 仅保证父节点优先级高于 / 低于子节点(满足堆的性质)
  • 不保证整个集合是完全有序的

例如,一个小顶堆的内部存储可能是这样的(逻辑结构):

    1
   / \
  3   2
 / \
4   5

其物理存储(数组)可能是 [1,3,2,4,5],显然数组本身并不是完全有序的。

当使用iterator()遍历这个数组时,会按照[1,3,2,4,5]的顺序访问,而非优先级顺序[1,2,3,4,5]

2. 设计取舍:牺牲遍历顺序换取操作效率

PriorityQueue的核心目标是高效地获取和删除优先级最高的元素poll()/peek()操作),这两个操作的时间复杂度是O(log n)

如果要保证iterator()能按顺序遍历,有两种方案,但都会带来性能问题:

  • 维护一个完全有序的数组 / 链表:插入元素时需要O(n)时间来保持有序性
  • 遍历前先对堆排序:会破坏堆结构,导致后续poll()等操作无法高效执行

因此,Java 的设计者选择不保证迭代顺序,以维持PriorityQueue在核心操作上的高效性。

3. 如何按优先级顺序遍历?

如果需要按优先级顺序遍历元素,正确的做法是:

while (!pq.isEmpty()) {
    System.out.println(pq.poll()); // 每次取出优先级最高的元素
}

但这种方式会清空队列。如果需要保留原队列,可先复制一份再遍历:

PriorityQueue copy = new PriorityQueue<>(original);
while (!copy.isEmpty()) {
    System.out.println(copy.poll());
}

你可能感兴趣的:(java,排序算法,数据结构)