算法之美:二叉堆原理剖析及堆应用案例讲解及实现

什么是堆

        堆(Heap)是计算机科学中一类特殊的数据结构,通常是一个可以被看做一棵完全二叉树的数组对象。

完全二叉树

         只有最下面两层节点的度可以小于2,并且最下层的叶节点集中在靠左连续的边界,只允许最后一层有空缺结点且空缺在右边,完全二叉树需保证最后一个节点之前的节点都齐全;
        对任一结点,如果其右子树的深度为j,则其左子树的深度必为j或j+1

什么是大顶堆(最大堆)

        大顶堆是一种完全二叉树,其每个父节点的值都大于或等于其子节点的值,即根节点的值最大,每个节点的两个子节点顺序没做要求,和之前的二叉查找树不一样。

 什么是小顶堆(最小堆)

        小顶堆是一种完全二叉树,其每个父节点的值都小于或等于其子节点的值,即根节点的值最小。每个节点的两个子节点顺序没做要求,和之前的二叉查找树不一样

 

存储原理剖析

        1)一般升序采用大顶堆,降序采用小顶堆;

        2)堆是一种非线性结构,用数组来存储完全二叉树是非常节省空间,把堆看作一个数组
方便操作,一般数组的下标0不存储,直接从1节点存储;

        3)堆其实就是利用完全二叉树的结构来维护一个数组;

        4)数组中下标为 k 的节点:

                左子节点下标为 2*k 的节点;
                右子节点就是下标 为 2*k+1 的节点;
                父节点就是下标为 k/2 取整的节点;

        5)对节点在树中的上下移动

                arr[k] 向上移动一层,则让k = k/2
                arr[k] 向下移动一层,则让k=2 k 或 k=2 k+1

 堆的定义通过公式描述:

        大顶堆:arr[k] >= arr[2k+1] && arr[k] >= arr[2k] 
        小顶堆:arr[k] <= arr[2k+1] && arr[k] <= arr[2k]

 应用场景简单介绍

        优先级队列、高精度定时器、TopK问题  ...

        下面我们将以高精度定时器案例进行讲解和代码实现。

二叉堆构建流程

        新插入一个元素之后,就是往数组最后面追加数据,堆可能就不满足堆的特性,需要进行调整,重新满足堆的特点,即该公式:大顶堆:arr[k] >= arr[2k+1] && arr[k] >= arr[2k] ,小顶堆:arr[k] <= arr[2k+1] && arr[k] <= arr[2k],顺着节点所在的路径,向上对比,然后交换。

小顶堆演示动画:Heap Visualization (usfca.edu)

         往堆中插入新元素,就是往数组中从索引0或1开始依次存放数据,但是顺序需要满足堆的特性如何让堆满足:
     1)不断比较新节点 arr[k]和对应父节点arr[k/2]的大小,根据情况交互元素位置;
     2)直到找到的父节点比当前新增节点大则结束;
     3)无需关注同级节点;

 

 大顶堆编码实现

        大顶堆是一种完全二叉树,其每个父节点的值都大于或等于其子节点的值,即根节点的值最大。

public class Heap {//用数组存储堆中的元素private int[] items;//堆中元素的个数private int num;public Heap(int capacity) {//数组下标0不存储数据,所以容量+1this.items = new int[capacity + 1];this.num = 0;}/*** 判断堆中 items[left] 元素是否小于 items[right] 的元素*/private boolean rightBig(int left, int right) {return items[left] < items[right];}/*** 交换堆中的两个元素位置*/private void swap(int i, int j) {int temp = items[i];items[i] = items[j];items[j] = temp;}/*** 往堆中插入一个元素,默认是最后面,++num先执行,然后进行上浮判断操作*/public void insert(int value) {items[++num] = value;up(num);}/*** 使用上浮操作,新增元素后,重新堆化* 不断比较新节点 arr[k]和对应父节点arr[k/2]的大小,根据情况交互元素位置* 直到找到的父节点比当前新增节点大则结束* <p>* 数组中下标为 k 的节点* 左子节点下标为 2*k 的节点* 右子节点就是下标 为 2*k+1 的节点* 父节点就是下标为 k/2 取整的节点*/private void up(int k) {//父节点 在数组的下标是1,下标大于1都要比较while (k > 1) {//比较 父结点 和 当前结点 大小if (rightBig(k / 2, k)) {//当前节点大,则和父节点交互位置swap(k / 2, k);}// 往上一层比较,当前节点变为父节点k = k / 2;}}/*** 删除堆中最大的元素,返回这个最大元素*/public int delMax() {int max = items[1];//交换索引 堆顶的元素(数组索引1的)和 最大索引处的元素,放到完全二叉树中最右侧的元素,方便后续变为临时根结点// 为啥不能直接删除顶部元素,因为删除后会断裂,成为森林,所以需要先交互,再删除swap(1, num);//最大索引处的元素删除掉, num--是后执行,元素个数需要减少1items[num--] = 0;//通过下浮调整堆,重新堆化down(1);return max;}/*** 使用下沉操作,堆顶和最后一个元素交换后,重新堆化* 不断比较 节点 arr[k]和对应 左节点arr[2*k] 和 右节点arr[2*k+1]的大小,如果当前结点小,则需要交换位置* 直到找到 最后一个索引节点比较完成  则结束* <p>* 数组中下标为 k 的节点* 左子节点下标为 2*k 的节点* 右子节点就是下标 为 2*k+1 的节点* 父节点就是下标为 k/2 取整的节点*/private void down(int k) {//最后一个节点下标是numwhile (2 * k <= num) {//记录当前结点的左右子结点中,较大的结点int maxIndex;if (2 * k + 1 <= num) { //2 * k + 1 <= num 是判断 确保有右节点//比较当前结点下的左右子节点哪个大if (rightBig(2 * k, 2 * k + 1)) {maxIndex = 2 * k + 1;} else {maxIndex = 2 * k;}} else {maxIndex = 2 * k;}//比较当前结点 和 较大结点的值, 如果当前节点较大则结束if (items[k] > items[maxIndex]) {break;} else {//否则往下一层比较,当前节点k索引 变换为 子节点中较大的值swap(k, maxIndex);//变换k的值k = maxIndex;}}}//    测试public static void main(String[] args) {Heap heap = new Heap(20);heap.insert(42);heap.insert(48);heap.insert(93);heap.insert(21);heap.insert(90);heap.insert(9);heap.insert(3);heap.insert(40);heap.insert(32);int top;while ((top = heap.delMax()) != 0) {System.out.print(top + ",");}}
}

堆应用案例:高精度定时器

        实现一个定时器的任务存储,支持很多不同时间的定时任务,要求高精度,1秒级别执行

方案一:

        数据库建立一个task表,存储到数据库或Redis里面,每隔1秒扫描一遍定时任务列表,获取要执行的任务;

        缺点:每次都需要遍历整个定时任务列表,有些很久才执行的任务,也需要被IO遍历,浪费CPU,由于列表比较大,每次遍历都耗时多;

方案二:

        使用【小顶堆】数据结构存储,小顶堆的插入操作就是在最小堆最后插入一个节点,然后重新调整小顶堆的结构,每隔一秒扫下堆顶元素,删除堆顶元素进行执行任务即可,然后重新堆化
key规则是【年月日时分秒】比如:2024-03-29-23-41-22 作为key,堆顶的元素就是最小的。

优先级队列: 传统队列特性就是先进先出,支持优先级排序,优先级高的最先出队(大顶堆实现),优先级队列实现方式有多种,堆去实现属于高效的一种方案
Java的PriorityQueue就是通过二叉小顶堆实现,用一棵完全二叉树表示,通过数组来作为PriorityQueue的底层实现,JDK的PriorityQueue默认是最小堆,可以使用比较器来让它变成最大堆。

编码实现

public class MaxHeapPriorityQueue<T extends Comparable<T>> {/*** 用数组存储堆中的元素*/private T[] items;/*** 记录堆中的元素个数*/private int num;public MaxHeapPriorityQueue(int capacity) {//数组下标0,不存储数据,所有容量要+1this.items = (T[]) new Comparable[capacity+1];this.num = 0;}/*** 判断队列是否为空* @return*/public boolean isEmpty() {return num==0;}/*** 比较大小,item[left] 元素是否小于 item[right]的元素*/private boolean rightBig(int left, int right) {return items[left].compareTo(items[right])<0;}/*** 交互堆中两个元素的位置*/private void swap(int i, int j) {T temp = items[i];items[i] = items[j];items[j] = temp;}/*** 往堆中插入一个元素,默认是数组最后面,然后进行上浮操作,++num会先执行,第一个数组0索引不存储数据*/public void insert(T value) {items[++num] = value;up(num);}/*** 使用上浮操作,新增元素后,重新堆化* 不断比较新节点 arr[k]和对应父节点arr[k/2]的大小,根据情况交互元素位置* 直到找到的父节点比当前新增节点大则结束* <p>* 数组中下标为 k 的节点* 左子节点下标为 2*k 的节点* 右子节点就是下标 为 2*k+1 的节点* 父节点就是下标为 k/2 取整的节点*/private void up(int k) {//父节点,在线数组的下标是1,数组索引大于1都要比较while (k > 1) {//比较 父节点 和 当前节点 的大小if (rightBig(k / 2, k)) {//如果当前节点比父节点大,则交互位置swap(k / 2, k);}//当前节点往上一层,当前节点变成父节点k = k / 2;}}/*** 使用下沉操作,堆顶和最后一个元素交换后,重新堆化* 不断比较 节点 arr[k]和对应 左节点arr[2*k] 和 右节点arr[2*k+1]的大小,如果当前结点小,则需要交换位置* 直到找到 最后一个索引节点比较完成  则结束* <p>* 数组中下标为 k 的节点* 左子节点下标为 2*k 的节点* 右子节点就是下标 为 2*k+1 的节点* 父节点就是下标为 k/2 取整的节点*/private void down(int k) {// 最后一个节点的下标是numwhile (2 * k <= num) {//记录当前节点的左右子节点,较大的节点int maxIndex;if (2 * k + 1 <= num) {if (rightBig(2 * k, 2 * k + 1)) {maxIndex = 2 * k + 1;} else {maxIndex = 2 * k;}} else {maxIndex = 2 * k;}//比较当前节点和较大接的值,如果当前节点大则结束if (items[k].compareTo(items[maxIndex]) > 0) {break;} else {//否则往下一层比较,当前节点的k变为子节点中较大的值swap(k, maxIndex);k = maxIndex;}}}/*** 删除堆中最大的元素,并且返回这个元素** @return*/public T delMax() {T maxValue = items[1];//交换索引 堆顶的元素(数组索引1的)和 最大索引处的元素,放到完全二叉树中最右侧的元素,方便后续变为临时根结点// 为啥不能直接删除顶部元素,因为删除后会断裂,成为森林,所以需要先交互,再删除swap(1, num);//最大索引处的元素删除,num--是后执行,元素个数需要减少1items[num--] = null;//通过下沉操作,重新堆化down(1);return maxValue;}
}
public class Task implements Comparable<Task> {private int weight;private String name;public Task(String name,int weight){this.weight = weight;this.name = name;}public void doTask(){System.out.println(name+" task 运行,权重 = "+weight);}@Overridepublic int compareTo(Task task) {return this.weight - task.weight;}}
 public static void main(String[] args) {MaxHeapPriorityQueue<Task> queue = new MaxHeapPriorityQueue(20);queue.insert(new Task("99任务",99));queue.insert(new Task("88任务",88));queue.insert(new Task("200任务",200));queue.insert(new Task("70任务",70));queue.insert(new Task("300任务",300));queue.insert(new Task("10任务",10));//通过循环从队列中获取最大的元素while(!queue.isEmpty()){Task task = queue.poll();task.doTask();}}

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

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

相关文章

《科技创新与应用》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答&#xff1a; 问&#xff1a;《科技创新与应用》是什么级别期刊&#xff1f; 答&#xff1a;省级&#xff1b;主管单位&#xff1a;黑龙江省科学技术协会&#xff1b;主办单位&#xff1a;黑龙江省创联文化传媒有限公司 问&#xff1a;《科技创新与应用》是核心期刊…

如何在OceanBase的OCP多节点上获取日志

背景 在使用OceanBase的OCP的过程中&#xff0c;因各种因素&#xff0c;我们可能需要对当前页面进行跟踪。在单一ocp节点环境下&#xff0c;我们自然可以直接在该节点上查找所需的日志。然而&#xff0c;当我们的环境中部署了多个ocp节点时&#xff0c;在排查问题时就会变得相…

google浏览器网站不安全与网站的连接不安全怎么办?

使用google谷歌浏览器访问某些网站打开时google谷歌浏览器提示网站不安全,与网站的连接不安全,您之所以会看到此警告,是因为该网站不支持https造成的怎么办? 目录 1、打开谷歌google浏览器点击右上角【┇】找到设置

【Canvas与艺术】五角星光芒四射的效果展示

【关键点】 三一渐变式光芒的实现。 【效果】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>光芒四射</title><st…

【Vue】响应式系统和依赖收集跟踪原理

为什么要依赖收集&#xff1f; 先举个栗子&#x1f330; 我们现在有这么一个 Vue 对象。 new Vue({template: <div><span>{{text1}}</span> <span>{{text2}}</span> <div>,data: {text1: text1,text2: text2,text3: text3} });然后我们…

使用AOP实现打印日志

首先创建annotation.SystemLog类&#xff1a; package com.gjh.annotation;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;Target(ElementType.METHOD…

Polar靶场web(三)

期待得到某一件事物的时候&#xff0c;才是最美好的。 签到 发现不能提交&#xff0c;看一下f12 发现提交按钮被禁用了&#xff0c;且最大输入9个字符&#xff0c;我们可以改一下。 现随便提交一个发现要提交ilovejijcxy session文件包含 发现有文件包含&#xff0c;那先包含…

2.java openCV4.x 入门-hello OpenCV

专栏简介 &#x1f492;个人主页 &#x1f4f0;专栏目录 点击上方查看更多内容 &#x1f4d6;心灵鸡汤&#x1f4d6;我们唯一拥有的就是今天&#xff0c;唯一能把握的也是今天 &#x1f9ed;文章导航&#x1f9ed; ⬆️ 1.环境搭建 ⬇️ 3.Mat之构造函数与数据类型 hell…

【MySQL】DML的表操作详解:添加数据&修改数据&删除数据(可cv例题语句)

前言 大家好吖&#xff0c;欢迎来到 YY 滴MySQL系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…

[Linux]基础IO(上)--理解文件系统调用、文件描述符、万物皆文件

一、文件的理解 每种语言都有进行文件操作的函数接口&#xff0c;例如C语言的fopen、fwrite、fprintf等等&#xff0c;但是进行文件操作的前提是代码已经跑起来&#xff0c;因为文件的打开与关闭要通过CPU来运行程序代码&#xff0c;所以打开文件的本质是进程打开文件&#xff…

iMazing2024功能强大的iPhone和iPad管理工具

iMazing是一款功能强大的iPhone和iPad管理工具&#xff0c;确实可以作为iTunes的替代品进行数据备份。以下是一些关于iMazing的主要特点和功能&#xff1a; 设备备份&#xff1a;iMazing可以备份iOS设备上的所有数据&#xff0c;包括照片、视频、音乐、应用程序等。与iTunes相比…

使用uni-app开发微信小程序并实现页面间的跳转

一、下载需要的开发工具 HBuilderX 微信开发者工具 HBuilderX HBuilderX-高效极客技巧 (dcloud.io) 微信开发者工具 下载 / 开发版更新日志 (qq.com) 二、新建项目 通过vue-cli命令行创建项目 参考&#xff1a; uni-app官网 (dcloud.net.cn) 2.1全局安装 vue-cli npm i…

Mysql数据备份与恢复实战

文章目录 备份类型备份内容备份工具mysqldump备份 实战案例&#xff1a;恢复误删除的表准备工作2:30完全备份完全备份后更新数据表10:00误删students表需要恢复还原的状态开始还原恢复 为什么要备份&#xff1f; 备份是为了&#xff1a;灾难恢复&#xff1a;硬件故障、软件故障…

Leetcode的正确打开方式

很多新手朋友在学习完数据结构与算法之后&#xff0c;都想找个平台磨练自己的技艺。那么LeetCode绝对是不二之选。但是官网刷题不是很友好&#xff0c;那么今天给大家介绍一款刷LeetCode神器。也是未来工作之后的摸鱼神器。 leetcode-editor 本打工人的摸&#xff08;nei&am…

10分钟带你用openlab搭建web网站

准备工作 虚拟机恢复快照 关闭防火墙、下载httpd&#xff0c;启动httpd服务、显示httpd服务是否开启 [rootserver ~]# systemctl stop firewalld [rootserver ~]# yum install httpd -y [rootserver ~]# systemctl start httpd [rootserver ~]# systemctl enable httpd [root…

【Java初阶(六)下】封装 继承 多态

❣博主主页: 33的博客❣ ▶文章专栏分类: Java从入门到精通◀ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; 目录 1.前言2.多态2.1多态的概念2.2多态实现条件2.3重写2.3.1避免在构造方法中调用重写的方法 2.4向上转型和向下转型2.4.1向上转型2.4.2向下转型 3.抽象类3…

常见的数学方法

Math类表示数学类&#xff0c;其中的数学方法都被定义成为static形式&#xff0c;所以可以直接通过Math类的类名调用某个数学方法。语法格式&#xff1a; Math.xxx(参数)&#xff1b; 例题 输入n个整数a1,a2,a3,......an,求这n个数的最大值max&#xff0c;最小值min&#xff0…

记录个人学习golang路线(如何学习golang,如何转golang)

最近好久没更&#xff0c;在看兔兔的博客&#xff0c;学习golang&#xff0c;兔兔的文章&#xff0c;有一定的编程经验 && 初学golang者&#xff0c;一定要看&#xff0c;如果是其他语言转golang&#xff0c;那就必须要看了&#xff0c;可以帮助你了解golang的语法&…

再见 mysql_upgrade

在数据库管理的世界里&#xff0c;随着技术的不断进步和业务的不断发展&#xff0c;数据库的版本升级成为了一个不可避免的过程。 MySQL 作为业界领先的开源关系型数据库管理系统&#xff0c;其版本迭代与功能优化同样不容忽视。 而在这个过程中&#xff0c;升级工具就显得尤为…

政安晨:【Keras机器学习实践要点】(五)—— 通过子类化创建新层和模型

目录 介绍 安装 层级&#xff1a;状态&#xff08;权重&#xff09;与某些计算的组合 层可以有不可训练的重量 最佳实践&#xff1a;推迟权重的创建&#xff0c;直到输入的形状已知。 层可以递归组合 后端不可知层和特定后端层 add_loss()方法 可以选择在您的层上启用…