【数据结构】深入理解优先级队列与堆:从原理到应用

目录

前言

一、优先级队列概述

[1.1 基本概念](#1.1 基本概念)

[1.2 核心操作](#1.2 核心操作)

二、堆:优先级队列的底层实现

[2.1 堆的定义](#2.1 堆的定义)

[2.2 堆的存储](#2.2 堆的存储)

[2.3 堆的核心操作](#2.3 堆的核心操作)

[2.3.1 向下调整(Shift Down)](#2.3.1 向下调整(Shift Down))

[2.3.2 向上调整(Shift Up)](#2.3.2 向上调整(Shift Up))

[2.3.3 建堆(Heapify)](#2.3.3 建堆(Heapify))

[2.4 堆的插入与删除](#2.4 堆的插入与删除)

[三、Java 中的 PriorityQueue](#三、Java 中的 PriorityQueue)

[3.1 基本特性](#3.1 基本特性)

[3.2 常用构造方法](#3.2 常用构造方法)

[3.3 自定义比较器实现大根堆](#3.3 自定义比较器实现大根堆)

[3.4 扩容机制](#3.4 扩容机制)

四、堆的应用

[4.1 堆排序(Heap Sort)](#4.1 堆排序(Heap Sort))

[4.2 Top-K 问题](#4.2 Top-K 问题)

总结


前言

在计算机科学中,队列(Queue) 是一种经典的先进先出(FIFO)数据结构。然而,许多实际场景需要根据优先级处理元素,而非单纯遵循先后顺序。例如:

  • 手机游戏中,来电通知应优先于游戏逻辑处理;

  • 学生选座位时,成绩优异的同学可能拥有优先选择权。

为此,优先级队列(Priority Queue) 应运而生。它支持高效获取最高优先级元素与插入新元素,是许多高级算法与系统的核心组件。本文将系统讲解优先级队列的实现原理------堆(Heap) ,并结合 Java 中的 PriorityQueue 探讨其实际应用。


一、优先级队列概述

1.1 基本概念

优先级队列是一种抽象数据类型,其元素带有优先级,出队时优先级最高(或最低)的元素先被取出。它不遵循严格的 FIFO 规则,而是依据优先级动态调度。

1.2 核心操作

  • offer(E e):插入元素

  • poll():移除并返回优先级最高的元素

  • peek():查看优先级最高的元素(不移除)


二、堆:优先级队列的底层实现

2.1 堆的定义

堆是一种完全二叉树,满足以下性质之一:

  • 大根堆(Max Heap) :每个节点的值 ≥ 其子节点的值

  • 小根堆(Min Heap) :每个节点的值 ≤ 其子节点的值

数学定义

对于数组存储的堆,若下标从 0 开始,则满足:

  • 大根堆:Ki≥K2i+1Ki​≥K2i+1​ 且 Ki≥K2i+2Ki​≥K2i+2​

  • 小根堆:Ki≤K2i+1Ki​≤K2i+1​ 且 Ki≤K2i+2Ki​≤K2i+2​

2.2 堆的存储

由于堆是完全二叉树,可采用数组顺序存储,节省空间且支持快速定位:

  • 父节点下标:parent(i) = (i - 1) / 2

  • 左子节点下标:left(i) = 2*i + 1

  • 右子节点下标:right(i) = 2*i + 2

2.3 堆的核心操作

2.3.1 向下调整(Shift Down)

用于删除堆顶或重建堆。从指定节点开始,与其较小(小堆)或较大(大堆)的子节点交换,直至满足堆性质。

java 复制代码
public void shiftDown(int[] array, int parent) {
    int child = 2 * parent + 1;
    int size = array.length;
    while (child < size) {
        if (child + 1 < size && array[child + 1] < array[child]) {
            child++;
        }
        if (array[parent] <= array[child]) break;
        swap(array, parent, child);
        parent = child;
        child = 2 * parent + 1;
    }
}

时间复杂度:O(log⁡n)

2.3.2 向上调整(Shift Up)

用于插入元素。将新节点与其父节点比较,若违反堆性质则交换,直至根节点。

java 复制代码
public void shiftUp(int[] array, int child) {
    int parent = (child - 1) / 2;
    while (child > 0 && array[parent] > array[child]) {
        swap(array, parent, child);
        child = parent;
        parent = (child - 1) / 2;
    }
}
2.3.3 建堆(Heapify)

从最后一个非叶子节点开始,向前依次执行向下调整:

java 复制代码
public void createHeap(int[] array) {
    for (int i = (array.length - 2) / 2; i >= 0; i--) {
        shiftDown(array, i);
    }
}

建堆时间复杂度:O(n)O(n)(通过错位相减法可严格证明)

2.4 堆的插入与删除

  • 插入:将元素放至末尾,执行向上调整

  • 删除堆顶:将堆顶与末尾元素交换,删除末尾,再向下调整堆顶


三、Java 中的 PriorityQueue

3.1 基本特性

PriorityQueue 是 Java 集合框架提供的优先级队列实现:

  • 基于堆实现,默认小根堆

  • 线程不安全(线程安全版本:PriorityBlockingQueue

  • 不允许 null 元素,元素必须可比较

  • 动态扩容,无容量限制

3.2 常用构造方法

java 复制代码
// 默认小堆,初始容量11
PriorityQueue<Integer> q1 = new PriorityQueue<>();

// 指定初始容量
PriorityQueue<Integer> q2 = new PriorityQueue<>(100);

// 通过集合初始化
List<Integer> list = Arrays.asList(4, 3, 2, 1);
PriorityQueue<Integer> q3 = new PriorityQueue<>(list);

3.3 自定义比较器实现大根堆

java 复制代码
class IntCmp implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2 - o1; // 降序排列
    }
}

PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new IntCmp());

3.4 扩容机制

  • 容量 < 64:扩容为原来的 2 倍

  • 容量 ≥ 64:扩容为原来的 1.5 倍

  • 最大容量为 Integer.MAX_VALUE - 8


四、堆的应用

4.1 堆排序(Heap Sort)

步骤:

  1. 建堆:将无序数组构建成堆(升序建大堆,降序建小堆)

  2. 排序:重复将堆顶元素(最大/最小)与末尾交换,然后调整堆

时间复杂度 :O(nlog⁡n)
空间复杂度:O(1)(原地排序)

4.2 Top-K 问题

高效求解前 K 个最大或最小元素:

算法步骤(求前 K 个最小元素为例):

  1. 用前 K 个元素构建一个大根堆

  2. 遍历剩余元素,若当前元素小于堆顶,则替换堆顶并调整堆

  3. 遍历完成后,堆中即为最小的 K 个元素

时间复杂度 :O(nlog⁡K)
空间复杂度:O(K)

java 复制代码
public int[] smallestK(int[] arr, int k) {
    if (k <= 0) return new int[0];
    PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
    for (int num : arr) {
        if (maxHeap.size() < k) {
            maxHeap.offer(num);
        } else if (num < maxHeap.peek()) {
            maxHeap.poll();
            maxHeap.offer(num);
        }
    }
    int[] res = new int[k];
    for (int i = 0; i < k; i++) {
        res[i] = maxHeap.poll();
    }
    return res;
}

总结

优先级队列以其高效的动态优先级管理能力,广泛应用于操作系统调度、网络流量管理、实时系统等领域。堆作为其核心实现,结合了完全二叉树的结构优势与数组存储的空间效率,使得插入、删除均能在对数时间内完成。

掌握堆与优先级队列,不仅是学习数据结构的关键一步,更是解决 Top-K、流数据中位数、Dijkstra 算法等高级问题的基础。在实际开发中,合理选择 PriorityQueue 并理解其底层机制,能显著提升程序的性能与可维护性。

相关推荐
偷星星的贼112 小时前
C++中的状态机实现
开发语言·c++·算法
程序员敲代码吗2 小时前
C++中的组合模式实战
开发语言·c++·算法
Leo July2 小时前
【Java】Spring Cloud 微服务生态全解析与企业级架构实战
java·spring cloud
Marktowin2 小时前
SpringBoot项目的国际化流程
java·后端·springboot
墨雨晨曦883 小时前
2026/01/20 java总结
java·开发语言
汤姆yu3 小时前
基于springboot的直播管理系统
java·spring boot·后端
52Hz1183 小时前
二叉树理论、力扣94.二叉树的中序遍历、104.二叉树的最大深度、226.反转二叉树、101.对称二叉树
python·算法·leetcode
a努力。3 小时前
虾皮Java面试被问:分布式Top K问题的解决方案
java·后端·云原生·面试·rpc·架构
Shirley~~3 小时前
leetcode移除元素
javascript·数据结构·算法