数据结构 - PriorityQueue的底层原理及应用

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

当使用 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的任务

就像医院挂号:

  1. 急诊病人(优先级10)
  2. 普通门诊(优先级5)
  3. 体检(优先级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>()
}

记住:

  1. 底层是最小堆实现
  2. 插入和删除都是 O(log n)
  3. 适合需要动态维护有序元素的场景
  4. 常用于任务调度和数据流处理

最小堆的实现原理

🌟 形象比喻

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 是元素数量
    */
}

记住:

  1. 最小堆是一个完全二叉树
  2. 父节点总是比子节点小
  3. 根节点是最小值
  4. 主要操作是上浮和下沉

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 的原因:

  1. PriorityQueue 底层是小根堆
  2. 我们想要高优先级先出队
  3. 所以通过相反的比较逻辑实现大根堆的效果

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

或者像考试分数:

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)用来决定元素的顺序。

相关推荐
Dizzy.5174 分钟前
数据结构(查找)
数据结构·学习·算法
Jared_devin3 小时前
数据结构——模拟栈例题B3619
数据结构
sushang~3 小时前
leetcode21.合并两个有序链表
数据结构·链表
张胤尘4 小时前
C/C++ | 每日一练 (2)
c语言·c++·面试
超爱吃士力架5 小时前
MySQL 中的回表是什么?
java·后端·面试
sjsjs115 小时前
【数据结构-并查集】力扣1202. 交换字符串中的元素
数据结构·leetcode·并查集
WYF_1115016 小时前
数据结构——单向循环链表、双链表、双向循环链表
数据结构
Cutey9167 小时前
Vue 3 中 watch 的报错解析
前端·面试
一小路一7 小时前
Go Web 开发基础:从入门到实战
服务器·前端·后端·面试·golang
纯粹要努力8 小时前
前端跨域问题及解决方案
前端·javascript·面试