基于二叉堆实现的 PriorityQueue

news2025/4/18 12:34:09

基于二叉堆实现的 PriorityQueue 是一种常见的数据结构,广泛用于任务调度、路径搜索、事件模拟等场景。下面我将用 Java 语言实现一个简单的基于最小堆PriorityQueue,即优先级最小的元素先出队。


✅ 实现目标

  • 使用数组实现二叉最小堆(即父节点 ≤ 子节点)
  • 支持以下基本操作:
    • add(E element)
    • peek()
    • poll()
    • size()
    • 自动扩容

🚀 实现代码(Java)

import java.util.Arrays;
import java.util.NoSuchElementException;

public class MyPriorityQueue<E extends Comparable<E>> {
    private static final int DEFAULT_CAPACITY = 16;
    private Object[] heap;
    private int size;

    public MyPriorityQueue() {
        heap = new Object[DEFAULT_CAPACITY];
        size = 0;
    }

    public void add(E element) {
        if (element == null) throw new NullPointerException();
        ensureCapacity();
        heap[size] = element;
        siftUp(size);
        size++;
    }

    public E peek() {
        if (size == 0) throw new NoSuchElementException();
        return elementAt(0);
    }

    public E poll() {
        if (size == 0) throw new NoSuchElementException();
        E result = elementAt(0);
        heap[0] = heap[size - 1];
        heap[size - 1] = null;
        size--;
        siftDown(0);
        return result;
    }

    public int size() {
        return size;
    }

    private void ensureCapacity() {
        if (size >= heap.length) {
            heap = Arrays.copyOf(heap, heap.length * 2);
        }
    }

    private void siftUp(int index) {
        Object temp = heap[index];
        while (index > 0) {
            int parent = (index - 1) / 2;
            if (((E) temp).compareTo(elementAt(parent)) >= 0) break;
            heap[index] = heap[parent];
            index = parent;
        }
        heap[index] = temp;
    }

    private void siftDown(int index) {
        Object temp = heap[index];
        int half = size / 2;
        while (index < half) {
            int left = 2 * index + 1;
            int right = left + 1;
            int smallest = left;
            if (right < size && elementAt(right).compareTo(elementAt(left)) < 0) {
                smallest = right;
            }
            if (((E) temp).compareTo(elementAt(smallest)) <= 0) break;
            heap[index] = heap[smallest];
            index = smallest;
        }
        heap[index] = temp;
    }

    @SuppressWarnings("unchecked")
    private E elementAt(int index) {
        return (E) heap[index];
    }
}

📦 示例用法

public class Main {
    public static void main(String[] args) {
        MyPriorityQueue<Integer> pq = new MyPriorityQueue<>();
        pq.add(5);
        pq.add(2);
        pq.add(9);
        pq.add(1);

        while (pq.size() > 0) {
            System.out.println(pq.poll());
        }
    }
}

输出:

1
2
5
9

如果你需要最大堆版本,只需把比较逻辑反过来即可。

下面是对上面代码的扩展版本,支持传入自定义的 Comparator,从而实现更灵活的排序逻辑,比如最大堆、自定义字段排序等。


✅ 改造目标

  • 支持传入 Comparator<E>,代替 Comparable<E> 进行元素比较。
  • 如果未传入 Comparator,则默认使用元素的自然顺序(即要求 E implements Comparable<E>)。

🚀 改造后的 Java 实现:支持 Comparator 的 PriorityQueue

import java.util.Arrays;
import java.util.Comparator;
import java.util.NoSuchElementException;

public class MyPriorityQueue<E> {
    private static final int DEFAULT_CAPACITY = 16;
    private Object[] heap;
    private int size;
    private final Comparator<? super E> comparator;

    public MyPriorityQueue() {
        this(null);
    }

    public MyPriorityQueue(Comparator<? super E> comparator) {
        this.comparator = comparator;
        this.heap = new Object[DEFAULT_CAPACITY];
        this.size = 0;
    }

    public void add(E element) {
        if (element == null) throw new NullPointerException();
        ensureCapacity();
        heap[size] = element;
        siftUp(size);
        size++;
    }

    public E peek() {
        if (size == 0) throw new NoSuchElementException();
        return elementAt(0);
    }

    public E poll() {
        if (size == 0) throw new NoSuchElementException();
        E result = elementAt(0);
        heap[0] = heap[size - 1];
        heap[size - 1] = null;
        size--;
        siftDown(0);
        return result;
    }

    public int size() {
        return size;
    }

    private void ensureCapacity() {
        if (size >= heap.length) {
            heap = Arrays.copyOf(heap, heap.length * 2);
        }
    }

    private void siftUp(int index) {
        Object temp = heap[index];
        while (index > 0) {
            int parent = (index - 1) / 2;
            if (compare((E) temp, elementAt(parent)) >= 0) break;
            heap[index] = heap[parent];
            index = parent;
        }
        heap[index] = temp;
    }

    private void siftDown(int index) {
        Object temp = heap[index];
        int half = size / 2;
        while (index < half) {
            int left = 2 * index + 1;
            int right = left + 1;
            int smallest = left;
            if (right < size && compare(elementAt(right), elementAt(left)) < 0) {
                smallest = right;
            }
            if (compare((E) temp, elementAt(smallest)) <= 0) break;
            heap[index] = heap[smallest];
            index = smallest;
        }
        heap[index] = temp;
    }

    @SuppressWarnings("unchecked")
    private E elementAt(int index) {
        return (E) heap[index];
    }

    private int compare(E a, E b) {
        if (comparator != null) {
            return comparator.compare(a, b);
        }
        return ((Comparable<? super E>) a).compareTo(b);
    }
}

📦 示例用法

✅ 默认最小堆

MyPriorityQueue<Integer> pq = new MyPriorityQueue<>();
pq.add(4);
pq.add(1);
pq.add(7);
System.out.println(pq.poll()); // 输出 1

✅ 最大堆(传入自定义 Comparator)

MyPriorityQueue<Integer> maxHeap = new MyPriorityQueue<>((a, b) -> b - a);
maxHeap.add(4);
maxHeap.add(1);
maxHeap.add(7);
System.out.println(maxHeap.poll()); // 输出 7

✅ 自定义对象排序(按年龄升序)

class Person {
    String name;
    int age;

    Person(String name, int age) { this.name = name; this.age = age; }

    public String toString() {
        return name + "(" + age + ")";
    }
}

MyPriorityQueue<Person> peopleQueue = new MyPriorityQueue<>(
    Comparator.comparingInt(p -> p.age)
);

peopleQueue.add(new Person("Alice", 30));
peopleQueue.add(new Person("Bob", 25));
peopleQueue.add(new Person("Carol", 35));

System.out.println(peopleQueue.poll()); // 输出 Bob(25)

如果你还有其他定制需求,比如:

  • 支持删除任意元素
  • 支持更新某个元素的优先级
  • 支持构造函数初始化时传入一个集合

基于二叉堆实现的 PriorityQueue 因其高效的插入和删除最小(或最大)元素操作,在很多应用场景中都有着广泛的运用。下面我详细介绍几个常见的应用场景:


1. 图算法:路径搜索与最短路径

Dijkstra 算法

在 Dijkstra 算法中,优先队列用来维护从起点到各个节点的当前最短路径距离。每次从优先队列中选出距离最小的节点,然后根据这个节点更新其邻接点的距离。借助基于二叉堆的 PriorityQueue,其对插入和删除操作的平均时间复杂度为 O(log n),使得算法在稀疏图中的效率大为提升。

A* 搜索算法

类似于 Dijkstra 算法,A* 算法也需要一个优先队列来维护待搜索节点,并根据预估函数 f(n)=g(n)+h(n) 的值来排列节点的优先级。使用二叉堆实现的 PriorityQueue 能够快速选取当前估值最低的节点,从而提高搜索效率。


2. 事件调度和仿真系统

离散事件模拟

在离散事件仿真系统中,每个事件都有一个触发时间,需要按照时间顺序执行。使用二叉堆实现的 PriorityQueue,可以将所有待执行事件按照时间顺序排列,每次只取最早触发的事件进行处理。这样的调度机制保证了事件的正确时序,适用于物流调度、网络仿真、制造系统仿真等场景。

操作系统任务调度

操作系统调度程序中也经常用到优先队列。比如在多任务系统中,任务可能具有不同的优先级或者截止时间。基于二叉堆的 PriorityQueue 可以迅速确定下一个需要处理的任务,有助于实现实时性要求较高的调度算法。


3. 排序和 Top-K 问题

堆排序

堆排序算法就是利用堆这种数据结构实现的排序算法。首先将数据构成一个堆,然后不断取出堆顶元素(最小/最大),并将剩余元素重新调整成堆结构。堆排序的时间复杂度为 O(n log n),适合在部分特殊场景下使用。

查找 Top-K 元素

在海量数据中查找前 K 个最小或最大的元素,可以利用一个固定大小的 PriorityQueue。对每个新数据,和队列中的最值进行比较,若满足条件就进行替换操作,这样能够在 O(n log k) 的时间内找出前 K 个元素,非常适合实时数据处理与流式计算。


4. 网络和流量管理

网络路由与转发

在网络数据包调度和路由选择中,优先队列可以根据数据包的紧急程度、传输延迟等属性来决定数据包的传输顺序。使用基于二叉堆的实现,能够有效管理大量数据包,保证网络传输的高效和公平性。

服务质量(QoS)调控

在一些需要区分服务等级的场景(例如 VOIP、视频流处理等),采用 PriorityQueue 可以确保高优先级数据(如实时音视频数据)优先传输,从而改善用户体验。


5. 金融和交易系统

高频交易系统

金融领域中的高频交易系统通常需要实时处理大量订单,并保证订单按照某种优先级(如价格、时间等)进行匹配。PriorityQueue 能够高效维护待处理的订单列表,使得交易处理的响应时间尽可能低。

风险管理

在风险评估和管理场景中,不同投资组合或交易策略可能具有不同的风险度量值。利用 PriorityQueue,可以快速锁定风险较高的部分并及时采取措施,实现高效监控和决策。


总结

基于二叉堆实现的 PriorityQueue 以其简单、高效的特性,在图算法、事件调度、任务调度、排序以及网络、金融等众多领域都有重要应用。其对插入、删除操作时间复杂度为 O(log n) 的优势,使得在数据量较大、实时性要求较高的场景中表现优异。通过支持自定义比较器,PriorityQueue 更加灵活,可以方便地应用在各种复杂的业务场景中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2331747.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JVM中常见的垃圾回收器(Garbage Collectors)

JVM中常见的垃圾回收器&#xff08;Garbage Collectors&#xff09;的分类和描述&#xff1a; 一、新生代收集器&#xff08;Young Generation Collectors&#xff09; 新生代收集器主要负责收集新创建的对象&#xff0c;这些对象通常存活时间较短。 Serial GC • 单线程收集…

极空间NAS进阶玩法:Debian 系统安装教程

文章目录 第 1 步:下载 Debian 镜像第 2 步:创建虚拟机创建虚拟机安装操作系统第 3 步:登录 Debian第 4 步:使用 Docker 搭建跳板机远程访问参考🚀 本文目标:在极空间 NAS 中安装 Debian 12。 第 1 步:下载 Debian 镜像 下载地址:https://www.debian.org/distrib/ 第…

煤矿数据机房防静电地板:智能化时代的“隐形守护者”

在煤矿行业&#xff0c;调度室不仅是安全生产的“大脑”&#xff0c;更是数据交互的“神经中枢”。随着智能化升级&#xff0c;如今的煤矿调度室早已不再是传统的电话挂图配置&#xff0c;而是集成了高清监控、精准定位系统、智能传感器等高精密电子设备的数字化空间。然而&…

操作符详解(下)——包含整形提升

1.讲解剩下的操作符 1.1:逗号表达式 逗号表达式&#xff0c;就是用逗号隔开的多个表达式。 逗号表达式&#xff0c;从左向右依次执⾏。整个表达式的结果是最后⼀个表达式的结果 例题1&#xff1a; //C的值是多少&#xff1f; int main() {int a 1;int b 2;int c (a &g…

Kairos 的野望:构建“智能体即服务”生态,让万物皆可 “Agent”

随着 AI Agent 成为 AI 领域的主要叙事&#xff0c;AI 赛道的发展也逐渐进入到 2.0 时代。聚焦于 AI Agent 概念本身&#xff0c;其是一种具备感知环境、进行决策和执行任务或服务的智能系统&#xff0c;它们通常能够理解自然语言指令&#xff0c;学习用户偏好&#xff0c;并在…

LeetCode 2968.执行操作使频率分数最大

给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 你可以对数组执行 至多 k 次操作&#xff1a; 从数组中选择一个下标 i &#xff0c;将 nums[i] 增加 或者 减少 1 。 最终数组的频率分数定义为数组中众数的 频率 。 请你返回你可以得到的 最大 频率分数。 众数指的…

多模态智能体框架MM-StoryAgent:跨模态叙事视频生成的技术突破

一、研究背景与核心价值 由上海交通大学与阿里巴巴联合研发的MM-StoryAgent系统,基于多智能体协同框架实现了故事创作到视频生成的完整自动化流程。该系统通过整合文本、视觉、语音、音效等多模态生成技术,构建了包含角色一致性保持、跨模态适配优化等创新机制的叙事内容生产…

Codeforces Round 1013 (Div. 3)

Problem - A - Codeforces 解题思路&#xff1a; 对每个需要的数字进行计数 #include<bits/stdc.h> using namespace std;int main() {int t;cin >> t;while (t--){int n;cin >> n;int two 2;int zero 3;int five 1;int three 1;int one 1;int flag …

银河麒麟系统虚拟机网络ping不通的解决方法

问题描述&#xff1a;使用NAT模式搭建了银河麒麟系统虚拟主机&#xff0c;虚拟机内部可以联网&#xff0c;可以查询到具体的ip地址&#xff0c;同时也可以在虚拟机内部ping同宿主机ip&#xff0c;但使用宿主机却无法ping同银河麒麟虚拟机ip&#xff0c;使用ssh、ftp、sftp等工具…

大数据学习(105)-大数据组件分析

&#x1f34b;&#x1f34b;大数据学习&#x1f34b;&#x1f34b; &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一…

基于SpinrgBoot+Vue的医院管理系统-026

一、项目技术栈 Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SpringBoot 前端&#xff1a;Vue开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 二、功能介绍 (1)…

LLM 为什么使用ID,每个单词不都是有编码的吗

LLM 为什么使用ID,每个单词不都是有编码的吗 在自然语言处理(NLP)里,把文本转换为整数 ID 来表示是一种常见的做法,以下为你详细阐述使用 ID 的原因,以及是否每个单词都有编码。 使用 ID 的原因 1. 计算机可处理性 计算机没办法直接处理文本数据,因为文本是人类使用的…

转行嵌入式,需要自学多久?

作为一个本硕都学机械&#xff0c;却阴差阳错进入嵌入式行业的老兵&#xff0c;这个问题我能聊一整天。十几年前我还在工厂车间穿着工装和机床打交道&#xff0c;偶然接触到单片机后就一发不可收拾。 转行这条路我走得异常艰辛&#xff0c;踩过的坑比写过的代码还多。去年我终…

【Leetcode-Hot100】移动零

题目 解答 首先&#xff0c;使用的解题思路是&#xff1a;使用两个指针&#xff0c;分别指向数组的第一个0元素位置&#xff0c;以该元素位置1为起始点寻找接下来第一个非0元素位置。二者确定后&#xff0c;对其进行交换。随后继续寻找下一个0元素位置。重复上述操作。 但第一…

leetcode_203. 移除链表元素_java

203. 移除链表元素https://leetcode.cn/problems/remove-linked-list-elements/ 1、题目 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head …

常见算法模板总结

文章目录 一、二叉树1. DFS2. BFS 二、回溯模板三、记忆化搜索四、动态规划1. 01背包朴素版本滚动数组优化 2. 完全背包朴素版本滚动数组优化 3. 最长递增子序列LIS朴素版本贪心二分优化 4. 最长公共子序列5. 最长回文子串 五、滑动窗口六、二分查找七、单调栈八、单调队列九、…

UE5学习笔记 FPS游戏制作44 统一UI大小 sizeBox

如果我们希望多个类似的UI大小一样&#xff0c;例如不同菜单的标题&#xff0c;可以使用sizeBox组件 我们在标题控件上&#xff0c;用sizeBox包裹所有子物体 然后指定他的最小宽高&#xff0c;或最大宽高 如果指定的是最小宽高&#xff0c;当子元素&#xff08;如图片&#xf…

C++学习之服务器EPOLL模型、处理客户端请求、向客户端回复数、向客户端发送文件

目录 1.启动epoll模型 2.和客户端建立新连接 3.接受客户端Http请求数据 4.代码回顾从接受的数据中读出请求行 5.请求行解析 6.正则表达式以及匹配 7.解析请求行以及后续处理 8.对path处理说明 9.如何回复响应数据 10.对文件对应content-type如何查询 11.服务器处理流…

BUUCTF-web刷题篇(17)

26.BabyUpload 源码&#xff1a;https://github.com/imaginiso/GXY_CTF/tree/master/Web/babyupload 查看题目源码&#xff1a; 写着&#xff1a;SetHandler application/x-httpd-php 通过源码可以看出这道文件上传题目主要还是考察.htaccess配置文件的特性&#xff0c;倘若…

国网B接口协议调阅实时视频接口流程详解以及检索失败原因(电网B接口)

文章目录 一、B接口协议调阅实时视频接口介绍B.6.1 接口描述B.6.2 接口流程B.6.3 接口参数B.6.3.1 SIP头字段B.6.3.2 SIP响应码B.6.3.3 SDP参数定义B.6.3.4 RTP动态Payload定义 B.6.4 消息示例B.6.4.1 调阅实时视频请求B.6.4.2 调阅实时视频请求响应 二、B接口调阅实时视频失败…