PriorityQueue的基本原理
🌟 基本概念
kotlin
/*
想象一个医院急诊室的排队系统:
- 普通队列:先来先服务
- 优先队列:按照病情严重程度优先处理
*/
// 基本实现
class PriorityQueue<T : Comparable<T>> {
// 底层使用最小堆实现
private val heap = ArrayList<T>()
// 插入元素
fun offer(element: T) {
heap.add(element)
siftUp(heap.size - 1) // 上浮操作
}
// 获取并移除最高优先级元素
fun poll(): T? {
if (heap.isEmpty()) return null
val result = heap[0]
heap[0] = heap.last()
heap.removeAt(heap.lastIndex)
if (heap.isNotEmpty()) {
siftDown(0) // 下沉操作
}
return result
}
}
💡 核心操作
kotlin
// 1. 上浮操作(新元素插入时使用)
private fun siftUp(index: Int) {
var current = index
while (current > 0) {
val parentIndex = (current - 1) / 2
// 如果当前节点比父节点小,则交换
if (heap[current] < heap[parentIndex]) {
heap.swap(current, parentIndex)
current = parentIndex
} else break
}
}
// 2. 下沉操作(删除根节点时使用)
private fun siftDown(index: Int) {
var current = index
while (true) {
val leftChild = 2 * current + 1
val rightChild = 2 * current + 2
var smallest = current
// 找到最小的子节点
if (leftChild < heap.size &&
heap[leftChild] < heap[smallest]) {
smallest = leftChild
}
if (rightChild < heap.size &&
heap[rightChild] < heap[smallest]) {
smallest = rightChild
}
if (smallest == current) break
heap.swap(current, smallest)
current = smallest
}
}
⚡ 实际应用示例
kotlin
// 1. 任务调度系统
data class Task(
val priority: Int,
val name: String
) : Comparable<Task> {
override fun compareTo(other: Task): Int {
return other.priority - priority // 高优先级在前
}
}
class TaskScheduler {
private val taskQueue = PriorityQueue<Task>()
fun addTask(task: Task) {
taskQueue.offer(task)
}
fun processNextTask() {
taskQueue.poll()?.let { task ->
println("Processing task: ${task.name}")
}
}
}
// 2. 医院急诊系统
data class Patient(
val severity: Int, // 病情严重程度
val name: String
) : Comparable<Patient> {
override fun compareTo(other: Patient): Int {
return other.severity - severity
}
}
class EmergencyRoom {
private val patientQueue = PriorityQueue<Patient>()
fun addPatient(patient: Patient) {
patientQueue.offer(patient)
}
fun treatNextPatient() {
patientQueue.poll()?.let { patient ->
println("Treating patient: ${patient.name}")
}
}
}
Comparable说明下
让我用具体例子来解释 other.priority - priority
的比较逻辑:
kotlin
// 假设我们有几个任务
val task1 = Task(priority = 10, name = "紧急bug修复")
val task2 = Task(priority = 5, name = "新功能开发")
val task3 = Task(priority = 1, name = "文档更新")
// 当比较两个任务时:
task1.compareTo(task2) // 10和5比较
// other.priority(5) - priority(10) = -5
// 负数意味着task1应该排在前面
task2.compareTo(task3) // 5和1比较
// other.priority(1) - priority(5) = -4
// 负数意味着task2应该排在前面
形象地说,就像排队:
graph LR
A[优先级10
紧急bug] --> B[优先级5
新功能] --> C[优先级1
文档] style A fill:#f96 style B fill:#9cf style C fill:#9f9
紧急bug] --> B[优先级5
新功能] --> C[优先级1
文档] style A fill:#f96 style B fill:#9cf style C fill:#9f9
当使用 PriorityQueue 时:
kotlin
val taskQueue = PriorityQueue<Task>()
taskQueue.add(Task(1, "文档更新"))
taskQueue.add(Task(10, "紧急bug修复"))
taskQueue.add(Task(5, "新功能开发"))
// 取出顺序:
println(taskQueue.poll()) // 优先级10的任务
println(taskQueue.poll()) // 优先级5的任务
println(taskQueue.poll()) // 优先级1的任务
就像医院挂号:
- 急诊病人(优先级10)
- 普通门诊(优先级5)
- 体检(优先级1)
other.priority - priority
的结果:
- 负数:当前任务排前面
- 正数:当前任务排后面
- 零:优先级相等
这样就能实现高优先级的任务优先处理。
🔄 性能分析
kotlin
class PriorityQueuePerformance {
/*
时间复杂度:
- 插入(offer): O(log n)
- 删除(poll): O(log n)
- 查看(peek): O(1)
空间复杂度:
- O(n),其中 n 是元素数量
*/
fun performanceDemo() {
val pq = PriorityQueue<Int>()
// 插入操作
repeat(1000) {
pq.offer(it) // O(log n)
}
// 删除操作
repeat(1000) {
pq.poll() // O(log n)
}
}
}
📊 实际应用场景
kotlin
// 1. Top K 问题
class TopK {
fun findTopK(nums: IntArray, k: Int): List<Int> {
val pq = PriorityQueue<Int>() // 最小堆
nums.forEach { num ->
pq.offer(num)
if (pq.size > k) {
pq.poll() // 保持堆大小为 k
}
}
return pq.toList()
}
}
// 2. 合并多个有序链表
class MergeLists {
data class ListNode(
val value: Int,
var next: ListNode? = null
) : Comparable<ListNode> {
override fun compareTo(other: ListNode): Int {
return value - other.value
}
}
fun mergeKLists(lists: Array<ListNode?>): ListNode? {
val pq = PriorityQueue<ListNode>()
// 添加所有链表的头节点
lists.filterNotNull().forEach {
pq.offer(it)
}
val dummy = ListNode(0)
var current = dummy
while (pq.isNotEmpty()) {
val node = pq.poll()!!
current.next = node
current = node
node.next?.let { pq.offer(it) }
}
return dummy.next
}
}
⚠️ 使用注意事项
kotlin
// 1. 自定义比较器
class CustomComparator {
// 使用自定义比较逻辑
val pq = PriorityQueue<Task> { t1, t2 ->
when {
t1.priority != t2.priority ->
t2.priority - t1.priority
else -> t1.name.compareTo(t2.name)
}
}
}
// 2. 线程安全考虑
class ThreadSafePQ {
// 需要线程安全时使用
val pq = PriorityBlockingQueue<Task>()
}
记住:
- 底层是最小堆实现
- 插入和删除都是 O(log n)
- 适合需要动态维护有序元素的场景
- 常用于任务调度和数据流处理
最小堆的实现原理
🌟 形象比喻
kotlin
/*
想象一个公司的组织架构:
- CEO在顶部(根节点)
- 每个主管下面带领若干员工
- 特点:每个主管的级别都比下属小(最小堆)
或者想象一个倒立的树:
- 最小的值在树根(顶部)
- 每个父节点都比其子节点小
*/
// 基本结构
class MinHeap<T : Comparable<T>> {
private val elements = ArrayList<T>()
// 获取父节点索引
private fun parent(index: Int) = (index - 1) / 2
// 获取左子节点索引
private fun leftChild(index: Int) = 2 * index + 1
// 获取右子节点索引
private fun rightChild(index: Int) = 2 * index + 2
}
💡 可视化示例
kotlin
/*
最小堆的树形结构:
3 层级0
/ \
5 4 层级1
/ \ /
10 7 6 层级2
数组表示:[3, 5, 4, 10, 7, 6]
*/
// 可视化实现
class HeapVisualizer {
fun visualize(heap: List<Int>) {
var level = 0
var count = 0
var maxNodes = 1
heap.forEach { value ->
print("$value ")
count++
if (count == maxNodes) {
println() // 换行
level++
count = 0
maxNodes *= 2
}
}
}
}
⚡ 核心操作
kotlin
// 1. 插入操作(上浮)
class MinHeap<T : Comparable<T>> {
fun insert(value: T) {
// 1. 先将新元素添加到末尾
elements.add(value)
// 2. 执行上浮操作
siftUp(elements.lastIndex)
}
private fun siftUp(index: Int) {
var current = index
// 如果当前节点比父节点小,就交换
while (current > 0) {
val parentIdx = parent(current)
if (elements[current] < elements[parentIdx]) {
elements.swap(current, parentIdx)
current = parentIdx
} else break
}
}
}
// 2. 删除最小值(下沉)
fun extractMin(): T? {
if (elements.isEmpty()) return null
// 1. 保存根节点(最小值)
val min = elements[0]
// 2. 将最后一个元素移到根部
elements[0] = elements.last()
elements.removeAt(elements.lastIndex)
// 3. 执行下沉操作
if (elements.isNotEmpty()) {
siftDown(0)
}
return min
}
🎯 生动示例
kotlin
// 模拟公司职级系统
data class Employee(
val level: Int,
val name: String
) : Comparable<Employee> {
override fun compareTo(other: Employee) = level - other.level
}
class CompanyStructure {
private val hierarchy = MinHeap<Employee>()
fun addEmployee(employee: Employee) {
hierarchy.insert(employee)
println("${employee.name} 加入公司")
// 自动调整到合适的位置
}
fun promoteNextPerson() {
hierarchy.extractMin()?.let { employee ->
println("${employee.name} 获得晋升机会")
}
}
}
// 使用示例
fun main() {
val company = CompanyStructure()
// 添加员工
company.addEmployee(Employee(5, "张三"))
company.addEmployee(Employee(3, "李四"))
company.addEmployee(Employee(4, "王五"))
// 晋升
company.promoteNextPerson() // 李四获得晋升机会
}
🔄 堆的调整过程
kotlin
// 堆调整的动画演示
class HeapAnimation {
/* 插入值 2 的过程
初始状态:
3
/ \
5 4
插入 2:
3
/ \
5 4
/
2
上浮后:
2
/ \
5 4
/
3
*/
fun demonstrateInsertion() {
val heap = MinHeap<Int>()
heap.insert(3)
heap.insert(5)
heap.insert(4)
heap.insert(2) // 观察上浮过程
}
}
⚠️ 实际应用
kotlin
// 1. 优先级任务处理器
class TaskProcessor {
private val taskHeap = MinHeap<Task>()
fun addTask(priority: Int, description: String) {
taskHeap.insert(Task(priority, description))
}
fun processNextTask() {
taskHeap.extractMin()?.let { task ->
println("处理任务: ${task.description}")
}
}
}
// 2. 考试成绩排序
class ScoreManager {
private val scoreHeap = MinHeap<Score>()
fun addScore(score: Int, studentName: String) {
scoreHeap.insert(Score(score, studentName))
}
fun getTopScores(count: Int): List<Score> {
val result = mutableListOf<Score>()
repeat(count) {
scoreHeap.extractMin()?.let {
result.add(it)
}
}
return result
}
}
💡 性能特点
kotlin
class HeapPerformance {
/*
时间复杂度:
- 插入:O(log n)
- 删除最小值:O(log n)
- 获取最小值:O(1)
空间复杂度:
- O(n),其中 n 是元素数量
*/
}
记住:
- 最小堆是一个完全二叉树
- 父节点总是比子节点小
- 根节点是最小值
- 主要操作是上浮和下沉
PriorityQueue底层小根堆和大根堆的说明
PriorityQueue 默认是小根堆实现的,但通过不同的比较逻辑可以实现大根堆的效果。让我解释下:
kotlin
// 1. 默认小根堆行为
data class Task1(val priority: Int) : Comparable<Task1> {
override fun compareTo(other: Task1): Int {
return priority - other.priority // 小的在前
}
}
// 2. 通过比较逻辑实现大根堆效果
data class Task2(val priority: Int) : Comparable<Task2> {
override fun compareTo(other: Task2): Int {
return other.priority - priority // 大的在前
}
}
小根堆示例:
graph TD
A[1] --> B[4]
A --> C[2]
B --> D[6]
B --> E[5]
C --> F[3]
style A fill:#f96
kotlin
val minHeap = PriorityQueue<Task1>()
minHeap.add(Task1(4))
minHeap.add(Task1(1))
minHeap.add(Task1(6))
minHeap.add(Task1(2))
// 出队顺序:1, 2, 4, 6
通过比较逻辑实现大根堆效果:
graph TD
A[6] --> B[4]
A --> C[5]
B --> D[1]
B --> E[2]
C --> F[3]
style A fill:#f96
kotlin
val maxHeap = PriorityQueue<Task2>()
maxHeap.add(Task2(4))
maxHeap.add(Task2(6))
maxHeap.add(Task2(1))
maxHeap.add(Task2(5))
// 出队顺序:6, 5, 4, 1
所以在我们的任务优先级例子中:
kotlin
data class Task(
val priority: Int,
val name: String
) : Comparable<Task> {
override fun compareTo(other: Task): Int {
return other.priority - priority // 通过这个比较逻辑
// 让高优先级的任务排在前面
// 虽然底层是小根堆,但表现出大根堆的效果
}
}
val taskQueue = PriorityQueue<Task>()
taskQueue.add(Task(1, "文档"))
taskQueue.add(Task(10, "紧急bug"))
taskQueue.add(Task(5, "新功能"))
// 出队顺序:
// 优先级10的任务(紧急bug)
// 优先级5的任务(新功能)
// 优先级1的任务(文档)
这就是为什么我们用 other.priority - priority
的原因:
- PriorityQueue 底层是小根堆
- 我们想要高优先级先出队
- 所以通过相反的比较逻辑实现大根堆的效果
compareTo()方法的判断逻辑
compareTo
的返回值是一个整数,代表两个对象的比较结果:
kotlin
data class Task(val priority: Int) : Comparable<Task> {
override fun compareTo(other: Task): Int {
return other.priority - priority
// 返回值:
// 负数 (-) : 当前对象应该排在前面
// 零 (0) : 两个对象相等
// 正数 (+) : 当前对象应该排在后面
}
}
让我用具体例子说明:
1. 负数:当前对象排前面
kotlin
val task1 = Task(10) // 高优先级
val task2 = Task(5) // 低优先级
task1.compareTo(task2)
// other.priority(5) - priority(10) = -5
// 负数,说明task1应该排在task2前面
// 形象表示:
// [task1, task2]
// [10, 5]
2. 零:两个对象相等
kotlin
val task1 = Task(5)
val task2 = Task(5)
task1.compareTo(task2)
// other.priority(5) - priority(5) = 0
// 零,说明优先级相等
// 形象表示:
// [task1 = task2]
// [5 = 5]
3. 正数:当前对象排后面
kotlin
val task1 = Task(5) // 低优先级
val task2 = Task(10) // 高优先级
task1.compareTo(task2)
// other.priority(10) - priority(5) = 5
// 正数,说明task1应该排在task2后面
// 形象表示:
// [task2, task1]
// [10, 5]
形象的比喻:
想象成排队:
graph LR
A[优先级10] --> B[优先级5] --> C[优先级5] --> D[优先级1]
subgraph "compareTo返回值"
E["10和5比较
返回-5
10排前面"] F["5和5比较
返回0
位置相等"] G["5和1比较
返回-4
5排前面"] end
返回-5
10排前面"] F["5和5比较
返回0
位置相等"] G["5和1比较
返回-4
5排前面"] end
或者像考试分数:
kotlin
data class Student(val score: Int) : Comparable<Student> {
override fun compareTo(other: Student): Int {
return score - other.score // 分数高的排前面
}
}
val student1 = Student(90)
val student2 = Student(85)
student1.compareTo(student2) // 返回5,表示90分排在85分前面
总结:
- 负数:当前对象 < 其他对象
- 零:当前对象 = 其他对象
- 正数:当前对象 > 其他对象
这个返回值会被集合(如PriorityQueue)用来决定元素的顺序。