优先队列的实现:基于最小堆的 Java 实现

优先队列是一种重要的数据结构,与普通队列不同,它每次从队列中取出的是具有最高优先级的元素。本文将介绍如何使用最小堆来实现优先队列,并提供详细的 Java 代码示例和解释。

什么是优先队列?

优先队列是一种抽象数据类型,其中每个元素都有一个与之相关的优先级。在删除操作中,总是删除具有最高优先级的元素(对于最小堆来说是最小值)。优先队列的典型应用包括任务调度、图算法(如 Dijkstra 算法)等。

为什么选择最小堆?

最小堆是一种完全二叉树,能够高效地进行插入和删除操作:

  • 插入操作:时间复杂度为 (O(\log n))。
  • 删除最小元素操作:时间复杂度为 (O(\log n))。

最小堆的这种高效性使其成为实现优先队列的理想选择。

构造树的选择:完全二叉树的数组表示

在实现最小堆时,我们有多种选择来构造树结构,但完全二叉树的数组表示方法是最为高效和适用的。以下是一些常见的树结构及其优缺点对比,说明我们为什么选择完全二叉树的数组表示。

实现方式 1:显式引用的树结构

Tree1A

public class Tree1A<Key> {Key k;Tree1A left;Tree1A middle;Tree1A right;...
}

优点:

  • 结构清晰,容易理解。
  • 方便直接操作每个节点和子节点。

缺点:

  • 每个节点都需要存储多个引用(left, middle, right),占用额外内存。
  • 遍历和查找操作的复杂度较高,尤其是对于非二叉树的情况。

Tree1B

public class Tree1B<Key> {Key k;Tree1B[] children;...
}

优点:

  • 结构较为灵活,可以处理任意数量的子节点。

缺点:

  • 需要为每个节点存储一个子节点数组,内存开销较大。
  • 访问子节点时需要遍历数组,效率较低。

Tree1C

public class Tree1C<Key> {Key k;Tree1C favoredChild;Tree1C sibling;...
}

优点:

  • 适用于偏斜树(如二叉堆),可以优化特定树结构的存储。

缺点:

  • 代码复杂度较高,尤其是在处理兄弟节点关系时。
  • 内存开销较大,需要存储额外的指针。
实现方式 2:父数组和键数组
public class Tree2<Key> {Key[] keys;int[] parents;...
}

优点:

  • 只需两个数组,存储效率高。
  • 可以直接通过数组索引访问父节点和子节点,访问效率高。

缺点:

  • 需要额外的父数组来存储父节点索引。
  • 对于完全二叉树,这种表示有些冗余,因为完全二叉树可以直接通过索引计算父子关系。
实现方式 3:完全二叉树的数组表示
public class TreeC<Key> {Key[] keys;...
}

优点:

  • 存储效率最高,只需要一个数组,没有额外的指针或索引开销。
  • 通过简单的索引计算(例如,对于节点i,其左子节点为2i+1,右子节点为2i+2)可以高效访问节点。

缺点:

  • 适用于完全二叉树,对于不完全二叉树,需要处理“间隙”问题。
  • 不适合表示非完全二叉树或其他非规则树结构。

结论

对于堆这种完全二叉树结构,用于优先队列的实现时,**实现方式 3(完全二叉树的数组表示)**是最优选择。

理由如下:

  • 存储效率:只需要一个数组来存储节点,无需额外存储指针或索引。
  • 访问效率:通过索引计算可以高效访问父节点和子节点。
  • 代码简单性:实现和维护简单,不需要复杂的指针操作。

在实现优先队列时,由于堆的性质决定了其是一个完全二叉树,完全二叉树的数组表示方式最为高效和简单,因此是最佳选择。

最小堆优先队列的实现

我们将用数组实现一个最小堆,以下是关键方法的详细解释和代码。

1. 数据结构

首先,我们定义了一个泛型类 MinHeapPriorityQueue,用来存储我们的最小堆:

import java.util.NoSuchElementException;public class MinHeapPriorityQueue<Key extends Comparable<Key>> {private Key[] keys; // 用于存储堆元素的数组private int size; // 当前堆中的元素数量// 构造函数,初始化优先队列,指定初始容量public MinHeapPriorityQueue(int capacity) {keys = (Key[]) new Comparable[capacity];size = 0;}// 检查堆是否为空public boolean isEmpty() {return size == 0;}// 返回堆中的元素数量public int size() {return size;}// 返回节点 k 的父节点索引private int parent(int k) {return (k - 1) / 2;}// 返回节点 k 的左子节点索引private int leftChild(int k) {return 2 * k + 1;}// 返回节点 k 的右子节点索引private int rightChild(int k) {return 2 * k + 2;}// 交换索引 i 和 j 处的元素private void swap(int i, int j) {Key temp = keys[i];keys[i] = keys[j];keys[j] = temp;}
2. 核心操作:swim 和 sink

swim 方法用于在插入元素时维护堆的性质,通过将新插入的元素上浮到适当位置:

    // 上浮操作,用于维持堆的性质private void swim(int k) {while (k > 0 && keys[parent(k)].compareTo(keys[k]) > 0) {swap(k, parent(k));k = parent(k);}}

sink 方法用于在删除最小元素时维护堆的性质,通过将替换到根位置的元素下沉到适当位置:

    // 下沉操作,用于维持堆的性质private void sink(int k) {while (leftChild(k) < size) {int j = leftChild(k);if (j < size - 1 && keys[j].compareTo(keys[j + 1]) > 0) {j++;}if (keys[k].compareTo(keys[j]) <= 0) {break;}swap(k, j);k = j;}}
3. 添加和删除操作

add 方法用于向优先队列添加新元素:

    // 向堆中添加新元素public void add(Key key) {if (size == keys.length) {resize(2 * keys.length);}keys[size] = key;swim(size);size++;}

getSmallest 方法用于获取堆顶元素,即最小元素:

    // 获取堆顶元素(最小元素)public Key getSmallest() {if (isEmpty()) {throw new NoSuchElementException("Priority queue underflow");}return keys[0];}

removeSmallest 方法用于删除并返回堆顶元素:

    // 删除并返回堆顶元素(最小元素)public Key removeSmallest() {if (isEmpty()) {throw new NoSuchElementException("Priority queue underflow");}Key min = keys[0];swap(0, size - 1);size--;sink(0);keys[size] = null; // 避免对象游离if (size > 0 && size == keys.length / 4) {resize(keys.length / 2);}return min;}
4. 扩容和缩容

为了确保数组具有足够的空间存储新元素,当数组已满时需要扩容,当数组元素较少时进行缩容:

    // 调整数组容量private void resize(int capacity) {Key[] temp = (Key[]) new Comparable[capacity];for (int i = 0; i < size; i++) {temp[i] = keys[i];}keys = temp;}
}

结论

通过上述实现,我们完成了一个基于最小堆的优先队列。此实现不仅高效,而且简单易懂,适用于大多数需要优先队列的数据结构和算法应用场景。无论是插入还是删除操作,时间复杂度均为 (O(\log n)),这使得最小堆成为实现优先队列的理想选择。

通过学习和实现最小堆优先队列,我们不仅掌握了优先队列的基本原理,还深入理解了堆这种数据结构的高效性和适用性。希望本文对您有所帮助,能够更好地理解和应用优先队列。

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

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

相关文章

使用Aspose技术将Excel/Word转换为PDF

简介&#xff1a;本文将介绍如何使用Aspose技术将Excel文件转换为PDF格式。我们将使用Aspose-Cells-8.5.2.jar包&#xff0c;并演示Java代码以及进行测试。 一、Aspose技术概述 Aspose是一款强大的文档处理库&#xff0c;支持多种编程语言&#xff0c;如Java、C#、Python等。…

关于 spring boot 的 目录详解 和 配置文件 以及 日志

目录 配置文件 spring boot 的配置文件有两种格式&#xff0c;分别是 properties 和 yml&#xff08;yaml&#xff09;。这两种格式的配置文件是可以同时存在的&#xff0c;此时会以 properties 的文件为主&#xff0c;但一般都是使用同一种格式的。 格式 properties 语法格…

小程序中用于跳转页面的5个api是什么?区别

小程序中用于跳转页面的5个API及其区别如下&#xff1a; wx.navigateTo(options) 功能&#xff1a;保留当前页面&#xff0c;跳转到应用内的某个页面&#xff0c;使用wx.navigateBack可以返回到原页面。特性&#xff1a;可以打开新的页面&#xff0c;新页面可以是tabBar页面&a…

【Python】selenium 点击某个按钮 click() 出现的报错问题--ElementClickInterceptedException(全!)

写在前面&#xff1a; 我们在使用selenium 点击某个元素时或者获取find_element的某个网页元素时&#xff0c;总会遇到一些问题。本人经验是&#xff0c;最直接的方法是用try_except 报错&#xff0c;直接绕过问题&#xff0c;可以直接看第一条。如果有兴趣具体解决&#xff0c…

[Algorithm][动态规划][01背包问题][模板 背包][分割等和子集]详细讲解 +何为背包问题?

目录 0.何为背包问题&#xff1f;1.模板 背包1.题目链接2.算法原理详解3.代码实现 2.分割等和子集1.题目链接2.算法原理详解3.代码实现 0.何为背包问题&#xff1f; 背包问题&#xff1a;有限制条件下的"组合问题" 你有一个背包&#xff0c;地上有一堆物品&#xff…

ASP .NET Core 中的 Autofac 依赖注入

介绍 Autofac 是适用于 .NET 应用程序&#xff08;包括 ASP.NET Core&#xff09;的流行依赖注入 (DI) 容器。Autofac 等 DI 容器通过提供注册和解析依赖关系的方法来帮助管理应用程序不同组件之间的依赖关系。 为什么需要Autofac&#xff0c;它能实现什么&#xff1f; 控制…

交错数组知识点

基本概念 交错数组是数组的数组&#xff0c;每个维度的数量可以不同。 注意&#xff1a;二维数组的每行的列数相同&#xff0c;交错数组每行的列数可能不同。 数组的申明 //变量类型[][] 交错数组名; int[][] arr1;//变量类型[][] 交错数组名 new 变量类型[行数][]; int[][…

四十三、openlayers官网示例Freehand Drawing解析——在地图上自由绘制图形

想要在地图上绘制自由图形&#xff0c;只需要在new Draw的时候多加一个配置项就行。 function addInteraction() {const value typeSelect.value;if (value ! "None") {draw new Draw({source: source,type: typeSelect.value,freehand: true, //是否自由绘制});ma…

在当前页面拿到抽屉弹窗页面中从后端返回的值 #Vue3 #两个.vue页面之间传值问题

在当前页面拿到抽屉弹窗页面中从后端返回的值 #Vue3 #两个.vue页面之间传值问题 *解决方法一&#xff1a; 将抽屉弹窗里从后端返回得到的值缓存在浏览器中&#xff0c;在当前页面中从浏览器中获取该值。 &#xff08;原理其实就是借助第三个盒子来传递一下值&#xff0c;太小学…

HIK录像机GB28181对接相机不在线问题随笔

一、问题现象 【设备信息】型号&#xff1a;DS-8664N-I16-V3 V4.63.000 build 230412 【问题现象】HIK录像机使用GB28181对接异常相机无法正常上线&#xff0c;对接HIK相机可以正常上线。 【现场拓扑】现场拓扑如下 NVR侧使用固定公网IP地址。IPC侧使用家用宽带的方式&…

第R3周:天气预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客 &#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 &#x1f680; 文章来源&#xff1a;K同学的学习圈子 目录 我的环境 语言环境&#xff1a;python3.8.18编译器&#xff1a;jupyter not…

pikachu靶场(File Inclusion(文件包含)通关教程)

1.File Inclusion(local)本地文件包含 1.1打开网站&#xff0c;发现有个下拉框&#xff0c;随便选择一个&#xff0c;然后点击提交 1.2发现图中有个参数变了&#xff0c;其他的也会变&#xff0c;猜测这里可能有其他隐藏的文件 1.3直接进行抓包 &#xff0c;右键发送到爆破模…

LeetCode | 1470.重新排列数组

class Solution(object):def shuffle(self, nums, n):""":type nums: List[int]:type n: int:rtype: List[int]"""result []for i in range(n):result.append(nums[i])result.append(nums[i n])return result这题很容易想到的就是遍历整个数组…

拼多多第37期:拼多多单品裂变起爆2.0(17节课)

课程下载&#xff1a;拼多多第37期&#xff1a;拼多多单品裂变起爆2.0&#xff08;17节课&#xff09;-课程网盘链接提取码下载.txt资源-CSDN文库 更多资源下载&#xff1a;关注我。 课程内容&#xff1a; 01.《拼多多单品裂变起爆2.0》非标品类成功操作案例.mp4 02.《拼多…

【Python爬虫单点登录实战】PyExecJS破解慧职教:过河源技术学院单点登录统一身份认证

目录 前言大致分析PyExecJS 使用案例pip 安装:Demo:输出:案例1.访问目标网站的登录页面并查看源码2.将js放到和py脚本同一级目录下3. 编写Python脚本来调用js破解单点登录实战提取密钥参数清洗数据登陆测试单点登录获取ticket获取jsessionid获取token成功我的专栏前言 博主提供…

小程序中 使用 UDPSocke通讯的流程

小程序使用UDPSocket与设备通讯的步骤可以归纳如下&#xff1a; 创建UDPSocket实例&#xff1a; 使用wx.createUDPSocket()方法创建一个UDP Socket实例。 绑定端口&#xff1a; 调用UDPSocket.bind(number port)方法&#xff0c;绑定一个系统随机分配的可用端口&#xff0c;或…

java 子网掩码和IP计算起始IP和终止IP工具类

java 子网掩码和IP计算起始IP和终止IP工具类 1.根据掩码位数获取掩码 2.根据ip地址和掩码获取起始IP 3.根据ip地址和掩码获取终止IP 4.ip转换Long 5.实际可用ip数量 public class NetAddressUtils {/*** 根据掩码位数获取掩码*/public static String getNetMask(String ma…

写一个经典的java引用传递的例子

private fun getRaidType(): List<SelectItem<String>> {var list raidTyperaidType.forEach{Log.i("DHG","raidType is $it")}list.forEach{Log.i("DHG","list raidType is $it")}viewModel.diskInfo?.value?.forEach…

SOLIDWORKS参数化设计插件 慧德敏学

SOLIDWORKS软件是法国达索公司的产品&#xff0c;最初是满足欧美一些工程师产品设计需要而开发的&#xff0c;并没有考虑中国的企业实际情况。我们为满足国内客户的需要&#xff0c;对SOLIDWORKS进行了二次开发&#xff0c;借助SolidKits.AutoWorks参数化工具&#xff0c;通过一…

.Net Core 8.0 IIS部署遇到奇怪的部分接口报404的问题解决

本地运行没问题&#xff0c;部署到IIS后&#xff0c;部分接口报404&#xff0c;其它接口都正常。 经和群里讨论&#xff0c;大概意思是接口返回数据比较大的时候&#xff0c;就会出现这个问题。 查看事件查看器&#xff0c;发现应该是数据过大时使用了临时文件夹&#xff0c;…