18-🎯 数据结构与算法核心知识 | 优先级队列:基于堆的高效调度数据结构

9 阅读20分钟
mindmap
  root((优先级队列))
    理论基础
      定义与特性
        优先级出队
        堆实现
        动态优先级
      历史发展
        1960s提出
        堆排序
        广泛应用
    实现方式
      最大堆
        高优先级先出
        堆顶最大
      最小堆
        低优先级先出
        堆顶最小
      其他实现
        斐波那契堆
        二项堆
    核心操作
      enqueue入队
        Olog n
        堆插入
      dequeue出队
        Olog n
        堆删除
      peek查看
        O1
        堆顶元素
    应用场景
      任务调度
        操作系统
        分布式系统
      图算法
        Dijkstra
        最短路径
      数据流处理
        Top K
        中位数
    工业实践
      Java PriorityQueue
        堆实现
        泛型支持
      Python heapq
        最小堆
        内置模块
      操作系统调度
        进程调度
        优先级管理

目录

一、前言

1. 研究背景

优先级队列(Priority Queue)是一种特殊的队列,元素按照优先级而非入队顺序出队。优先级队列通常使用堆(Heap)实现,在任务调度、图算法、数据流处理等领域有广泛应用。

根据IEEE的研究,优先级队列是操作系统和分布式系统的核心数据结构。Linux的进程调度、Dijkstra最短路径算法、Top K问题等都使用优先级队列实现。

2. 历史发展

  • 1960s:优先级队列概念提出
  • 1964年:堆排序算法(优先级队列的应用)
  • 1970s:在操作系统中应用
  • 1990s至今:成为标准库的核心组件

二、概述

1. 什么是优先级队列

优先级队列(Priority Queue)是一种特殊的队列,元素按照优先级出队,而不是按照入队顺序。优先级高的元素先出队,优先级低的元素后出队。

三、什么是优先级队列

优先级队列(Priority Queue)是一种特殊的队列,元素按照优先级出队,而不是按照入队顺序。

优先级队列的示意图

优先级队列(最大堆):
       10(高优先级)
       /  \
      8    9
     / \  / \
    5  6 7  8

出队顺序: 10 → 9 → 8 → 8 → 7 → 6 → 5

优先级队列的操作

  • enqueue(e, priority): 添加元素,指定优先级
  • dequeue(): 取出优先级最高的元素
  • peek(): 查看优先级最高的元素
  • isEmpty(): 判断是否为空

四、优先级队列的特点

  1. 优先级出队:优先级高的元素先出队
  2. 动态优先级:元素的优先级可以动态调整
  3. 堆实现:通常使用堆(堆)实现

五、优先级队列的实现

基于堆的实现

优先级队列通常使用堆来实现,可以是最大堆或最小堆。

Java实现
public class PriorityQueue<E extends Comparable<E>> {
    private MaxHeap<E> heap;
    
    public PriorityQueue() {
        heap = new MaxHeap<>();
    }
    
    public void enqueue(E e) {
        heap.add(e);
    }
    
    public E dequeue() {
        return heap.extractMax();
    }
    
    public E peek() {
        return heap.findMax();
    }
    
    public int size() {
        return heap.size();
    }
    
    public boolean isEmpty() {
        return heap.isEmpty();
    }
}
Python实现
import heapq

class PriorityQueue:
    def __init__(self):
        self.heap = []
    
    def enqueue(self, item, priority):
        heapq.heappush(self.heap, (-priority, item))  # 使用负号实现最大堆
    
    def dequeue(self):
        if self.heap:
            priority, item = heapq.heappop(self.heap)
            return item
        raise IndexError("Queue is empty")
    
    def peek(self):
        if self.heap:
            return self.heap[0][1]
        raise IndexError("Queue is empty")
    
    def is_empty(self):
        return len(self.heap) == 0
    
    def size(self):
        return len(self.heap)

带优先级的元素

class PriorityElement<E> implements Comparable<PriorityElement<E>> {
    E element;
    int priority;
    
    public PriorityElement(E element, int priority) {
        this.element = element;
        this.priority = priority;
    }
    
    @Override
    public int compareTo(PriorityElement<E> other) {
        return Integer.compare(this.priority, other.priority);
    }
}
from dataclasses import dataclass
from typing import Generic, TypeVar

T = TypeVar('T')

@dataclass
class PriorityElement(Generic[T]):
    element: T
    priority: int
    
    def __lt__(self, other):
        return self.priority < other.priority

六、应用场景

1. 任务调度

# 操作系统任务调度
tasks = PriorityQueue()
tasks.enqueue("任务1", 5)
tasks.enqueue("任务2", 10)  # 高优先级
tasks.enqueue("任务3", 3)

# 按优先级执行
while not tasks.is_empty():
    task = tasks.dequeue()
    print(f"执行: {task}")

2. Dijkstra算法

# 最短路径算法
def dijkstra(graph, start):
    pq = PriorityQueue()
    distances = {start: 0}
    
    pq.enqueue(start, 0)
    
    while not pq.is_empty():
        current = pq.dequeue()
        
        for neighbor, weight in graph[current]:
            new_dist = distances[current] + weight
            
            if neighbor not in distances or new_dist < distances[neighbor]:
                distances[neighbor] = new_dist
                pq.enqueue(neighbor, new_dist)
    
    return distances

3. 合并K个有序链表

def merge_k_lists(lists):
    import heapq
    
    pq = []
    for i, lst in enumerate(lists):
        if lst:
            heapq.heappush(pq, (lst.val, i, lst))
    
    dummy = ListNode(0)
    current = dummy
    
    while pq:
        val, idx, node = heapq.heappop(pq)
        current.next = node
        current = current.next
        
        if node.next:
            heapq.heappush(pq, (node.next.val, idx, node.next))
    
    return dummy.next

4. 前K个高频元素

def top_k_frequent(nums, k):
    from collections import Counter
    import heapq
    
    counter = Counter(nums)
    pq = []
    
    for num, freq in counter.items():
        heapq.heappush(pq, (freq, num))
        if len(pq) > k:
            heapq.heappop(pq)
    
    return [num for _, num in pq]

5. 数据流的中位数

class MedianFinder:
    def __init__(self):
        self.max_heap = []  # 较小的一半
        self.min_heap = []  # 较大的一半
    
    def add_num(self, num):
        heapq.heappush(self.max_heap, -num)
        
        heapq.heappush(self.min_heap, -heapq.heappop(self.max_heap))
        
        if len(self.max_heap) < len(self.min_heap):
            heapq.heappush(self.max_heap, -heapq.heappop(self.min_heap))
    
    def find_median(self):
        if len(self.max_heap) == len(self.min_heap):
            return (-self.max_heap[0] + self.min_heap[0]) / 2.0
        else:
            return -self.max_heap[0]

七、时间复杂度分析

操作时间复杂度说明
插入O(log n)堆的插入操作
删除O(log n)堆的删除操作
查看最大值O(1)直接访问根节点
构建O(n)批量建堆

八、实际应用

Java中的PriorityQueue

PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(5);
pq.offer(2);
pq.offer(8);

// 最小堆,从小到大出队
while (!pq.isEmpty()) {
    System.out.println(pq.poll());  // 2, 5, 8
}

Python中的heapq

import heapq

# 最小堆
heap = []
heapq.heappush(heap, 5)
heapq.heappush(heap, 2)
heapq.heappush(heap, 8)

# 从小到大出队
while heap:
    print(heapq.heappop(heap))  # 2, 5, 8

九、优先级队列的理论基础

1. 形式化定义(根据CLRS定义)

定义

优先级队列Q是一个数据结构,支持以下操作:

  • ENQUEUE(Q, x, p): 将元素x以优先级p加入队列
  • DEQUEUE(Q): 取出并返回优先级最高的元素
  • PEEK(Q): 返回优先级最高的元素(不删除)
  • EMPTY(Q): 判断队列是否为空
  • SIZE(Q): 返回队列中元素的数量

数学表述

设优先级队列Q包含n个元素,每个元素e有优先级p(e):

  • 对于最大优先级队列:DEQUEUE(Q)返回argmax_{e∈Q} p(e)
  • 对于最小优先级队列:DEQUEUE(Q)返回argmin_{e∈Q} p(e)

学术参考

  • CLRS Chapter 6: Heapsort
  • Williams, J. W. J. (1964). "Algorithm 232: Heapsort." Communications of the ACM

2. 堆的性质与数学证明

优先级队列通常使用堆实现,堆满足堆序性质:

最大堆(Max-Heap)性质: 对于堆中的任意节点i(非根节点): A[i/2]A[i]A[\lfloor i/2 \rfloor] \geq A[i]

即:父节点的值 ≥ 子节点的值(高优先级先出)

最小堆(Min-Heap)性质: 对于堆中的任意节点i(非根节点): A[i/2]A[i]A[\lfloor i/2 \rfloor] \leq A[i]

即:父节点的值 ≤ 子节点的值(低优先级先出)

堆的高度

对于包含n个元素的堆,高度h满足: h=log2nh = \lfloor \log_2 n \rfloor

证明

  • 完全二叉树的第i层有2^i个节点
  • 总节点数:n=20+21+...+2h1+kn = 2^0 + 2^1 + ... + 2^{h-1} + k,其中0k<2h0 \leq k < 2^h
  • 因此:2hn<2h+12^h \leq n < 2^{h+1},即hlog2n<h+1h \leq \log_2 n < h+1
  • 所以:h=log2nh = \lfloor \log_2 n \rfloor

学术参考

  • CLRS Chapter 6.1: Heaps
  • Knuth, D. E. (1997). The Art of Computer Programming, Volume 3. Section 5.2.3: Heapsort

十、动态优先级调整

问题场景

某些应用中,元素的优先级需要动态调整。

伪代码:动态优先级调整

ALGORITHM UpdatePriority(queue, element, newPriority)
    // 问题:堆不支持O(log n)的优先级更新
    
    // 方案1:删除后重新插入(O(log n))
    queue.remove(element)
    queue.enqueue(element, newPriority)
    
    // 方案2:使用索引堆(支持O(log n)更新)
    index ← element.index
    queue.update(index, newPriority)

索引堆(Index Heap)

特点:支持O(log n)的优先级更新

伪代码:索引堆实现

STRUCT IndexHeap {
    data: Array[Element]      // 堆数据
    indexes: Array[int]       // 索引数组
    reverse: Array[int]       // 反向索引
}

ALGORITHM IndexHeapUpdate(heap, index, newPriority)
    // 更新元素优先级
    heap.data[index].priority ← newPriority
    
    // 上浮或下沉调整
    pos ← heap.reverse[index]
    SiftUp(heap, pos)
    SiftDown(heap, pos)

十一、工业界实践案例

1. 案例1:Java PriorityQueue的实现(Oracle/Sun Microsystems实践)

背景:Java的PriorityQueue使用最小堆实现,支持泛型。

技术实现分析(基于Oracle Java源码):

  1. 最小堆实现

    • 默认行为:默认最小堆,堆顶元素最小
    • 最大堆支持:可以通过自定义比较器实现最大堆
    • 堆序维护:使用上浮(siftUp)和下沉(siftDown)操作维护堆序
  2. 动态扩容策略

    • 初始容量:默认容量为11
    • 扩容策略:容量小于64时翻倍,否则增加50%
    • 扩容时机:当元素数超过容量时自动扩容
  3. 性能优化

    • 数组实现:使用数组存储,内存连续,缓存友好
    • 批量操作addAll()方法优化,减少多次上浮操作
    • 迭代器:支持快速失败(fail-fast)迭代器

性能数据(Oracle Java团队测试,1000万次操作):

操作PriorityQueue(堆)排序数组说明
插入O(log n)O(n)堆优势明显
删除O(log n)O(n)堆优势明显
获取最值O(1)O(1)性能相同
空间复杂度O(n)O(n)空间相同

学术参考

  • Oracle Java Documentation: PriorityQueue Class
  • Java Source Code: java.util.PriorityQueue
  • CLRS Chapter 6: Heapsort

伪代码:PriorityQueue的enqueue操作

ALGORITHM PriorityQueueOffer(element)
    // 扩容检查
    IF size >= capacity THEN
        Resize(capacity * 2)
    
    // 添加到数组末尾
    heap[size] ← element
    size ← size + 1
    
    // 上浮调整
    SiftUp(size - 1)

ALGORITHM SiftUp(index)
    WHILE index > 0 DO
        parent ← (index - 1) / 2
        
        IF Compare(heap[index], heap[parent]) >= 0 THEN
            BREAK  // 满足堆序性质
        
        Swap(heap[index], heap[parent])
        index ← parent

2. 案例2:操作系统的进程调度(Linux Foundation实践)

背景:操作系统使用优先级队列管理进程调度。

技术实现分析(基于Linux内核源码):

  1. 多级队列调度(MLFQ)

    • 队列分级:不同优先级的进程在不同队列中
    • 优先级映射:实时优先级(0-99)和普通优先级(100-139)
    • 调度策略:高优先级队列优先调度
  2. 动态优先级调整

    • 行为反馈:根据进程的I/O等待和CPU使用情况调整优先级
    • 交互式进程:I/O密集型进程优先级提升
    • CPU密集型进程:CPU密集型进程优先级降低
  3. 完全公平调度器(CFS)

    • 红黑树实现:使用红黑树管理运行队列
    • 虚拟运行时间:使用vruntime作为键,保证公平性
    • 时间片分配:根据进程权重分配时间片

性能数据(Linux内核测试,1000个进程):

调度算法平均响应时间CPU利用率公平性说明
先来先服务基准基准基准
优先级队列0.5×0.9×性能提升
CFS(红黑树)0.3×0.95×优秀最佳性能

学术参考

  • Linux Kernel Documentation: Process Scheduling
  • Linux Source Code: kernel/sched/core.c
  • Tanenbaum, A. S. (2014). Modern Operating Systems (4th ed.). Chapter 2: Process and Thread Management

伪代码:进程调度

ALGORITHM ScheduleProcess()
    // 从优先级队列中选择优先级最高的进程
    process ← priorityQueue.extractMax()
    
    IF process ≠ NULL THEN
        // 执行进程
        ExecuteProcess(process, timeSlice)
        
        // 更新优先级(根据执行情况)
        UpdatePriority(process)
        
        // 重新入队
        IF process.state = READY THEN
            priorityQueue.insert(process)

案例3:Dijkstra最短路径算法

背景:Dijkstra算法使用优先级队列实现单源最短路径。

伪代码:Dijkstra算法

ALGORITHM Dijkstra(graph, start)
    distances ← Map(start → 0)
    pq ← PriorityQueue()
    visited ← EmptySet()
    
    pq.enqueue(start, 0)
    
    WHILE NOT pq.isEmpty() DO
        current ← pq.dequeue()
        
        IF current IN visited THEN
            CONTINUE
        
        visited.add(current)
        
        // 更新邻居节点的距离
        FOR EACH (neighbor, weight) IN graph.getNeighbors(current) DO
            newDist ← distances[current] + weight
            
            IF neighbor NOT IN distances OR newDist < distances[neighbor] THEN
                distances[neighbor] ← newDist
                pq.enqueue(neighbor, newDist)
    
    RETURN distances

案例4:Top K问题的堆解法

背景:在大数据场景中,需要找出最大的K个元素。

优化方案

  1. 维护K个元素的最小堆:只保留最大的K个元素
  2. 空间复杂度:O(K),而非O(n)
  3. 时间复杂度:O(n log K)

伪代码:Top K问题

ALGORITHM TopK(nums, k)
    minHeap ← MinHeap(k)  // 大小为k的最小堆
    
    FOR EACH num IN nums DO
        IF minHeap.size < k THEN
            minHeap.insert(num)
        ELSE IF num > minHeap.peek() THEN
            minHeap.extractMin()  // 移除最小值
            minHeap.insert(num)   // 插入更大的值
    
    RETURN minHeap.toArray()

十二、应用场景详解

1. 任务调度系统

场景:分布式任务调度系统使用优先级队列管理任务。

伪代码

ALGORITHM TaskScheduler()
    taskQueue ← PriorityQueue()
    
    // 添加任务
    FUNCTION AddTask(task, priority)
        taskQueue.enqueue(task, priority)
    
    // 执行任务
    WHILE NOT taskQueue.isEmpty() DO
        task ← taskQueue.dequeue()
        ExecuteTask(task)

2. 数据流的中位数

场景:实时计算数据流的中位数。

伪代码

ALGORITHM MedianFinder()
    maxHeap ← MaxHeap()  // 较小的一半
    minHeap ← MinHeap()  // 较大的一半
    
    FUNCTION AddNum(num)
        maxHeap.insert(num)
        minHeap.insert(maxHeap.extractMax())
        
        IF maxHeap.size < minHeap.size THEN
            maxHeap.insert(minHeap.extractMin())
    
    FUNCTION FindMedian()
        IF maxHeap.size = minHeap.size THEN
            RETURN (maxHeap.peek() + minHeap.peek()) / 2.0
        ELSE
            RETURN maxHeap.peek()

3. 合并K个有序链表

场景:合并多个有序链表为一个有序链表。

伪代码

ALGORITHM MergeKLists(lists)
    pq ← PriorityQueue()
    
    // 将每个链表的头节点加入队列
    FOR EACH list IN lists DO
        IF list ≠ NULL THEN
            pq.enqueue(list, list.val)
    
    dummy ← NewListNode(0)
    current ← dummy
    
    WHILE NOT pq.isEmpty() DO
        node ← pq.dequeue()
        current.next ← node
        current ← current.next
        
        IF node.nextNULL THEN
            pq.enqueue(node.next, node.next.val)
    
    RETURN dummy.next

十三、总结

优先级队列是基于堆实现的高效调度数据结构,通过堆序性质实现了O(log n)的插入删除和O(1)的最值访问。从操作系统调度到图算法,从数据流处理到任务调度,优先级队列在多个领域都有重要应用。

关键要点

  1. 堆实现:优先级队列通常使用堆实现,保证O(log n)性能
  2. 优先级出队:按照优先级而非入队顺序出队
  3. 动态调整:某些场景需要支持动态优先级调整
  4. 广泛应用:任务调度、图算法、数据流处理等

延伸阅读

核心论文

  1. Williams, J. W. J. (1964). "Algorithm 232: Heapsort." Communications of the ACM, 7(6), 347-348.

    • 堆排序的原始论文,优先级队列的基础
  2. Dijkstra, E. W. (1959). "A note on two problems in connexion with graphs." Numerische Mathematik, 1(1), 269-271.

    • Dijkstra算法的原始论文,使用优先级队列

核心教材

  1. Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2009). Introduction to Algorithms (3rd ed.). MIT Press.

    • Chapter 6: Heapsort - 堆和优先级队列的详细理论
  2. Knuth, D. E. (1997). The Art of Computer Programming, Volume 3: Sorting and Searching (2nd ed.). Addison-Wesley.

    • Section 5.2.3: Heapsort - 堆排序和优先级队列
  3. Sedgewick, R. (2011). Algorithms (4th ed.). Addison-Wesley.

    • Chapter 2.4: Priority Queues - 优先级队列的实现

工业界技术文档

  1. Oracle Java Documentation: PriorityQueue Class

  2. Python官方文档:heapq Module

  3. Java Source Code: PriorityQueue Implementation

  4. Python Source Code: heapq Module

技术博客与研究

  1. Google Research. (2020). "Priority Queue Optimization in Large-Scale Systems."

  2. Facebook Engineering Blog. (2019). "Task Scheduling with Priority Queues."

十四、优缺点分析

优点

  1. 快速获取最值:O(1)时间获取最高优先级元素
  2. 高效插入删除:O(log n)时间完成操作
  3. 灵活应用:适用于多种场景(调度、算法、数据处理)
  4. 实现简单:基于堆实现,代码相对简单

缺点

  1. 不支持随机访问:无法访问中间元素,只能访问堆顶
  2. 内存开销:需要维护堆结构,空间开销较大
  3. 动态调整困难:普通堆不支持O(log n)的优先级更新
  4. 不完全有序:只保证堆序性质,不是完全有序

梦想从学习开始,事业从实践起步:理论是基础,实践是关键,持续学习是成功之道。

数据结构与算法是计算机科学的基础,是软件工程师的核心技能。 本系列文章旨在复习数据结构与算法核心知识,为人工智能时代,接触AIGC、AI Agent,与AI平台、各种智能半智能业务场景的开发需求做铺垫:


其它专题系列文章

1. 前知识

2. 基于OC语言探索iOS底层原理

3. 基于Swift语言探索iOS底层原理

关于函数枚举可选项结构体闭包属性方法swift多态原理StringArrayDictionary引用计数MetaData等Swift基本语法和相关的底层原理文章有如下几篇:

4. C++核心语法

5. Vue全家桶

其它底层原理专题

1. 底层原理相关专题

2. iOS相关专题

3. webApp相关专题

4. 跨平台开发方案相关专题

5. 阶段性总结:Native、WebApp、跨平台开发三种方案性能比较

6. Android、HarmonyOS页面渲染专题

7. 小程序页面渲染专题