优先队列(Priority Queue)是在计算机科学中一种非常有用的抽象数据类型,它与标准队列的主要区别在于元素的出队顺序不是先进先出(FIFO),而是基于每个元素的优先级。具有较高优先级的元素会比低优先级的元素更早出队。在Java中,PriorityQueue
类是实现优先队列的一种方式。
Java中的PriorityQueue
java.util.PriorityQueue
是Java集合框架的一部分,它实现了Queue
接口。PriorityQueue
底层使用了一个名为堆的数据结构,通常是一个二叉堆,以保证每次操作的时间复杂度为O(log n),其中n是队列中的元素数量。
主要特点:
元素排序:
PriorityQueue
默认按照自然排序(自然顺序)来排序元素,即元素必须实现Comparable
接口,或者在创建PriorityQueue
时提供一个Comparator
对象来定制排序规则。最小堆行为:
PriorityQueue
默认表现得像一个最小堆,即队列的头部元素是最小的元素。如果需要最大堆的行为,可以通过自定义比较器来实现。不允许null元素:
PriorityQueue
不允许插入null
元素。不支持随机访问:
PriorityQueue
不支持快速随机访问元素,因为它不是基于索引的结构。
常用方法:
add(E e)
:将指定的元素添加到此队列中。offer(E e)
:如果可能立即添加指定的元素而不会违反队列的容量限制,则添加;否则返回false。remove()
:检索并移除此队列的头;如果此队列为空,则抛出NoSuchElementException异常。poll()
:检索并移除此队列的头;如果此队列为空,则返回null。peek()
:检索但不移除此队列的头;如果此队列为空,则返回null。size()
:返回队列中的元素个数。isEmpty()
:如果队列中没有元素,则返回true。
示例代码:
下面是一个使用PriorityQueue
的例子,展示如何创建和使用一个优先队列:
import java.util.PriorityQueue; import java.util.Comparator; public class PriorityQueueExample { public static void main(String[] args) { // 创建一个基于数字大小的优先队列 PriorityQueue<Integer> pq = new PriorityQueue<>(); // 添加元素 pq.add(5); pq.add(1); pq.add(3); // 输出队列头部的元素 System.out.println("Head element: " + pq.peek()); // 输出最小的元素,这里是1 // 移除并输出队列头部的元素 System.out.println("Removed head element: " + pq.remove()); // 输出并移除最小的元素,这里是1 // 使用自定义比较器创建优先队列 PriorityQueue<String> pqStrings = new PriorityQueue<>(new Comparator<String>() { @Override public int compare(String s1, String s2) { return s2.compareTo(s1); // 反转字符串的排序顺序 } }); // 添加字符串元素 pqStrings.add("Apple"); pqStrings.add("Banana"); pqStrings.add("Cherry"); // 输出队列头部的元素 System.out.println("Head element: " + pqStrings.peek()); // 输出最大的元素,这里是"Cherry" } }
在这个例子中,我们创建了两个PriorityQueue
实例。第一个实例是基于整数的优先队列,按照数字大小排序。第二个实例是基于字符串的优先队列,使用自定义的比较器来反转字符串的自然排序顺序。
优先队列在很多场景下都非常有用,比如任务调度、事件驱动模拟、Dijkstra算法中的最短路径计算、以及各种基于优先级的系统中。
下面我会给出几个具体的场景以及相应的Java代码实现,以便你了解优先队列在实际编程中的应用方式。
场景1:任务调度
在任务调度场景中,优先级高的任务会被优先执行。这里我们使用PriorityQueue
来管理任务列表。
import java.util.PriorityQueue; class Task implements Comparable<Task> { private String name; private int priority; public Task(String name, int priority) { this.name = name; this.priority = priority; } public String getName() { return name; } public int getPriority() { return priority; } @Override public int compareTo(Task other) { return Integer.compare(this.priority, other.priority); } } public class TaskScheduler { private PriorityQueue<Task> tasks = new PriorityQueue<>(); public void addTask(Task task) { tasks.add(task); } public Task getNextTask() { return tasks.poll(); } public static void main(String[] args) { TaskScheduler scheduler = new TaskScheduler(); scheduler.addTask(new Task("Task1", 3)); scheduler.addTask(new Task("Task2", 1)); scheduler.addTask(new Task("Task3", 2)); while (!scheduler.tasks.isEmpty()) { Task task = scheduler.getNextTask(); System.out.println("Executing: " + task.getName()); } } }
场景2:Dijkstra算法
Dijkstra算法用于在加权图中寻找两个顶点之间的最短路径。优先队列在这里用来保存待访问的顶点,按照它们的当前最小距离排序。
import java.util.*; public class DijkstraAlgorithm { private Map<String, List<Edge>> graph = new HashMap<>(); private Map<String, Integer> distances = new HashMap<>(); private PriorityQueue<Edge> queue = new PriorityQueue<>(); public void addEdge(String source, String destination, int weight) { graph.computeIfAbsent(source, k -> new ArrayList<>()).add(new Edge(destination, weight)); } public void calculateShortestPaths(String startVertex) { distances.put(startVertex, 0); queue.add(new Edge(startVertex, 0)); while (!queue.isEmpty()) { Edge edge = queue.poll(); String vertex = edge.getDestination(); if (distances.containsKey(vertex)) continue; // Already processed distances.put(vertex, edge.getWeight()); for (Edge neighbor : graph.getOrDefault(vertex, Collections.emptyList())) { queue.add(new Edge(neighbor.getDestination(), distances.get(vertex) + neighbor.getWeight())); } } } public static void main(String[] args) { DijkstraAlgorithm dijkstra = new DijkstraAlgorithm(); dijkstra.addEdge("A", "B", 5); dijkstra.addEdge("A", "C", 10); dijkstra.addEdge("B", "D", 3); dijkstra.addEdge("C", "D", 1); dijkstra.addEdge("C", "E", 2); dijkstra.addEdge("D", "E", 1); dijkstra.addEdge("D", "F", 5); dijkstra.addEdge("E", "F", 5); dijkstra.calculateShortestPaths("A"); System.out.println(dijkstra.distances); } static class Edge implements Comparable<Edge> { private String destination; private int weight; public Edge(String destination, int weight) { this.destination = destination; this.weight = weight; } public String getDestination() { return destination; } public int getWeight() { return weight; } @Override public int compareTo(Edge other) { return Integer.compare(this.weight, other.weight); } } }
场景3:事件驱动的模拟
在事件驱动的模拟中,事件按照发生的时间排序。优先队列可以用来保持事件列表的有序性。
import java.util.PriorityQueue; class Event implements Comparable<Event> { private long time; private String description; public Event(long time, String description) { this.time = time; this.description = description; } public long getTime() { return time; } public String getDescription() { return description; } @Override public int compareTo(Event other) { return Long.compare(this.time, other.time); } } public class EventDrivenSimulation { private PriorityQueue<Event> events = new PriorityQueue<>(); public void scheduleEvent(long time, String description) { events.add(new Event(time, description)); } public void runSimulation() { while (!events.isEmpty()) { Event event = events.poll(); System.out.println("Time: " + event.getTime() + ", Event: " + event.getDescription()); } } public static void main(String[] args) { EventDrivenSimulation simulation = new EventDrivenSimulation(); simulation.scheduleEvent(1000, "Start"); simulation.scheduleEvent(1010, "Event 1"); simulation.scheduleEvent(1005, "Event 2"); simulation.runSimulation(); } }
在上述代码中,我们创建了三个不同的场景,每个场景都展示了PriorityQueue
在不同应用场景下的使用方式。希望这些示例能够帮助你更好地理解优先队列在实际编程中的作用。